Skip to main content

strided_fused/
fused.rs

1//! Runtime-DAG fused elementwise kernels.
2
3use core::mem::MaybeUninit;
4use strided_basic::execution::validate_destination_layout;
5#[cfg(test)]
6use strided_basic::execution::{is_injective_layout, validate_destination_layout_without_alloc};
7
8use crate::{Result, StridedError, StridedView, StridedViewMut};
9use strided_basic::execution::{
10    build_plan_fused, build_plan_fused_small, ensure_same_shape, for_each_inner_block_preordered,
11    SMALL_TENSOR_THRESHOLD,
12};
13use strided_basic::execution::{compute_costs, mapreduce_threaded, parallel_width, KernelClass};
14use strided_basic::execution::{
15    map_into_validated, zip_map2_into_validated, zip_map3_into_validated, zip_map4_into_validated,
16    ValidatedDestinationLayout,
17};
18use strided_basic::Exec;
19
20mod chunk;
21
22/// RAII owner of the initialized prefix of a stack buffer of destination
23/// views.
24///
25/// Used by the erased multi-output entry points so their stack-buffer cast stays
26/// out of the erased raw-boundary modules. Dropping the guard drops exactly the
27/// initialized prefix, so a rank above `INLINE_RANK` does not leak every
28/// destination's boxed metadata on success, error, or unwind.
29pub(crate) struct InitializedViewsMut<'a, T> {
30    storage: *mut MaybeUninit<StridedViewMut<'a, T>>,
31    len: usize,
32}
33
34impl<'a, T> InitializedViewsMut<'a, T> {
35    /// # Safety
36    /// `storage` must point to at least `OPERAND_CAP` writable slots that stay
37    /// valid until the guard is dropped.
38    pub(crate) unsafe fn new(storage: *mut MaybeUninit<StridedViewMut<'a, T>>) -> Self {
39        Self { storage, len: 0 }
40    }
41
42    /// Write the next initialized view into the prefix.
43    pub(crate) fn push(&mut self, view: StridedViewMut<'a, T>) {
44        debug_assert!(self.len < strided_basic::OPERAND_CAP);
45        // SAFETY: the caller reserved `OPERAND_CAP` slots and
46        // `len < OPERAND_CAP` holds.
47        unsafe { self.storage.add(self.len).write(MaybeUninit::new(view)) };
48        self.len += 1;
49    }
50
51    /// Borrow the initialized prefix.
52    pub(crate) fn as_mut_slice(&mut self) -> &mut [StridedViewMut<'a, T>] {
53        // SAFETY: every slot below `len` was initialized by `push`, and
54        // `MaybeUninit<StridedViewMut>` has the same layout as
55        // `StridedViewMut`.
56        unsafe {
57            core::slice::from_raw_parts_mut(self.storage.cast::<StridedViewMut<'a, T>>(), self.len)
58        }
59    }
60}
61
62impl<T> Drop for InitializedViewsMut<'_, T> {
63    fn drop(&mut self) {
64        for index in 0..self.len {
65            // SAFETY: every slot in the prefix was initialized by `push`.
66            unsafe { (*self.storage.add(index)).assume_init_drop() };
67        }
68    }
69}
70
71/// A raw operand pointer that is `Default` and `Send + Sync`, so pointer lists
72/// can live in an inline operand vector and be captured by worker closures.
73///
74/// `repr(transparent)` over `*mut T`, so a slice of them can be viewed as a
75/// slice of raw pointers without copying.
76#[repr(transparent)]
77#[derive(Clone, Copy, Debug)]
78pub(super) struct OperandPtr<T>(*mut T);
79
80impl<T> Default for OperandPtr<T> {
81    fn default() -> Self {
82        Self(core::ptr::null_mut())
83    }
84}
85
86// SAFETY: only used to move/copy validated operand addresses to worker threads;
87// the owning views establish liveness, aliasing and access at each use.
88unsafe impl<T> Send for OperandPtr<T> {}
89unsafe impl<T> Sync for OperandPtr<T> {}
90
91impl<T> OperandPtr<T> {
92    #[inline]
93    fn new(ptr: *mut T) -> Self {
94        Self(ptr)
95    }
96}
97
98/// View an inline operand vector as a `&[*mut T]` slice.
99#[inline]
100fn operand_ptrs<T>(ptrs: &[OperandPtr<T>]) -> &[*mut T] {
101    // SAFETY: `OperandPtr<T>` is `repr(transparent)` over `*mut T`.
102    unsafe { core::slice::from_raw_parts(ptrs.as_ptr().cast::<*mut T>(), ptrs.len()) }
103}
104
105/// View an inline operand vector as a `&[*const T]` slice.
106#[inline]
107fn operand_const_ptrs<T>(ptrs: &[OperandPtr<T>]) -> &[*const T] {
108    // SAFETY: `OperandPtr<T>` is `repr(transparent)` over `*mut T`, and
109    // `*mut T` and `*const T` have the same layout.
110    unsafe { core::slice::from_raw_parts(ptrs.as_ptr().cast::<*const T>(), ptrs.len()) }
111}
112
113/// Runtime scalar operation for a fused elementwise plan.
114#[derive(Clone, Copy, Debug, Eq, PartialEq)]
115pub enum FusedOp {
116    Add,
117    Multiply,
118    Negate,
119    Conj,
120    Divide,
121    Abs,
122    Maximum,
123    Minimum,
124    Clamp,
125    Exp,
126    Log,
127    Sin,
128    Cos,
129    Tanh,
130    Sqrt,
131    Rsqrt,
132    Pow,
133    Expm1,
134    Log1p,
135}
136
137impl FusedOp {
138    #[inline]
139    pub const fn label(self) -> &'static str {
140        match self {
141            Self::Add => "add",
142            Self::Multiply => "multiply",
143            Self::Negate => "negate",
144            Self::Conj => "conj",
145            Self::Divide => "divide",
146            Self::Abs => "abs",
147            Self::Maximum => "maximum",
148            Self::Minimum => "minimum",
149            Self::Clamp => "clamp",
150            Self::Exp => "exp",
151            Self::Log => "log",
152            Self::Sin => "sin",
153            Self::Cos => "cos",
154            Self::Tanh => "tanh",
155            Self::Sqrt => "sqrt",
156            Self::Rsqrt => "rsqrt",
157            Self::Pow => "pow",
158            Self::Expm1 => "expm1",
159            Self::Log1p => "log1p",
160        }
161    }
162}
163
164/// One SSA instruction in a [`FusedPlan`].
165///
166/// `inputs` holds at most [`OPERAND_CAP`](strided_basic::OPERAND_CAP) value ids
167/// inline and spills above that, so a bounded program builds without touching
168/// the heap.
169#[derive(Clone, Debug, Eq, PartialEq)]
170pub struct FusedInst {
171    pub op: FusedOp,
172    pub inputs: strided_basic::OperandVec<usize>,
173}
174
175impl FusedInst {
176    /// Build an instruction from an operation and its input value ids.
177    ///
178    /// ```
179    /// use strided_fused::{FusedInst, FusedOp};
180    /// let inst = FusedInst::new(FusedOp::Add, [0, 1]);
181    /// assert_eq!(inst.inputs.as_slice(), &[0, 1]);
182    /// ```
183    pub fn new(op: FusedOp, inputs: impl IntoIterator<Item = usize>) -> Self {
184        Self {
185            op,
186            inputs: inputs.into_iter().collect(),
187        }
188    }
189}
190
191impl Default for FusedOp {
192    fn default() -> Self {
193        Self::Add
194    }
195}
196
197impl Default for FusedInst {
198    fn default() -> Self {
199        Self {
200            op: FusedOp::default(),
201            inputs: strided_basic::OperandVec::new(),
202        }
203    }
204}
205
206/// Topologically ordered fused elementwise SSA DAG.
207///
208/// Values are numbered in evaluation order. Input values occupy
209/// `0..input_count`; each instruction appends one value after the previous
210/// inputs/instructions. For example, with `input_count == 2`, the first
211/// instruction writes value `2`, the second writes value `3`, and so on.
212/// `outputs` contains the value ids to write to `dests` in order.
213///
214/// The program is stored inline: at most eight instructions, five outputs and
215/// [`OPERAND_CAP`](strided_basic::OPERAND_CAP) inputs per instruction, spilling
216/// to the heap only above those bounds. Build one with [`FusedPlan::new`] to
217/// keep `vec!`-based construction ergonomic; the `pub` fields accept the inline
218/// collection directly. (Changing the field type from `Vec` is a 0.6.0 breaking
219/// change.)
220///
221/// All inputs and destinations passed to [`fused_elementwise_into`] must have
222/// the same shape and scalar type. Broadcast inputs should be represented with
223/// `StridedView::broadcast` before building the plan; the fused API does not
224/// perform implicit broadcasting.
225#[derive(Clone, Debug, Eq, PartialEq)]
226pub struct FusedPlan {
227    pub input_count: usize,
228    pub outputs: strided_basic::OperandVec<usize>,
229    pub ops: strided_basic::InlineVec<FusedInst>,
230}
231
232impl FusedPlan {
233    /// Build a plan from an input count, output value ids, and instructions.
234    ///
235    /// ```
236    /// use strided_fused::{FusedInst, FusedOp, FusedPlan};
237    /// let plan = FusedPlan::new(
238    ///     2,
239    ///     vec![3],
240    ///     vec![
241    ///         FusedInst::new(FusedOp::Add, vec![0, 1]),
242    ///         FusedInst::new(FusedOp::Negate, vec![2]),
243    ///     ],
244    /// );
245    /// assert_eq!(plan.ops.len(), 2);
246    /// ```
247    pub fn new(
248        input_count: usize,
249        outputs: impl IntoIterator<Item = usize>,
250        ops: impl IntoIterator<Item = FusedInst>,
251    ) -> Self {
252        Self {
253            input_count,
254            outputs: outputs.into_iter().collect(),
255            ops: ops.into_iter().collect(),
256        }
257    }
258}
259
260/// Scalar types supported by [`fused_elementwise_into`].
261pub trait FusedScalar: Copy + Send + Sync + 'static {
262    fn fused_dtype_label() -> &'static str {
263        core::any::type_name::<Self>()
264    }
265
266    fn supports_fused_op(_op: FusedOp) -> bool {
267        true
268    }
269
270    fn fused_add(self, rhs: Self) -> Self;
271    fn fused_multiply(self, rhs: Self) -> Self;
272    fn fused_negate(self) -> Self;
273    fn fused_conj(self) -> Self;
274    fn fused_divide(self, rhs: Self) -> Self;
275    fn fused_abs(self) -> Self;
276    fn fused_maximum(self, rhs: Self) -> Self;
277    fn fused_minimum(self, rhs: Self) -> Self;
278    fn fused_clamp(self, min: Self, max: Self) -> Self;
279    fn fused_exp(self) -> Self;
280    fn fused_log(self) -> Self;
281    fn fused_sin(self) -> Self;
282    fn fused_cos(self) -> Self;
283    fn fused_tanh(self) -> Self;
284    fn fused_sqrt(self) -> Self;
285    fn fused_rsqrt(self) -> Self;
286    fn fused_pow(self, rhs: Self) -> Self;
287    fn fused_expm1(self) -> Self;
288    fn fused_log1p(self) -> Self;
289}
290
291macro_rules! unsupported_fused_op {
292    ($op:literal, $ty:literal) => {
293        unreachable!("unsupported fused op {} for dtype {}", $op, $ty)
294    };
295}
296
297macro_rules! impl_real_fused_scalar {
298    ($ty:ty) => {
299        impl FusedScalar for $ty {
300            #[inline(always)]
301            fn fused_add(self, rhs: Self) -> Self {
302                self + rhs
303            }
304
305            #[inline(always)]
306            fn fused_multiply(self, rhs: Self) -> Self {
307                self * rhs
308            }
309
310            #[inline(always)]
311            fn fused_negate(self) -> Self {
312                -self
313            }
314
315            #[inline(always)]
316            fn fused_conj(self) -> Self {
317                self
318            }
319
320            #[inline(always)]
321            fn fused_divide(self, rhs: Self) -> Self {
322                self / rhs
323            }
324
325            #[inline(always)]
326            fn fused_abs(self) -> Self {
327                self.abs()
328            }
329
330            #[inline(always)]
331            fn fused_maximum(self, rhs: Self) -> Self {
332                // Same select as the ordinary binary kernel; see
333                // `strided_basic::nan_propagating_maximum`.
334                strided_basic::nan_propagating_maximum(self, rhs)
335            }
336
337            #[inline(always)]
338            fn fused_minimum(self, rhs: Self) -> Self {
339                strided_basic::nan_propagating_minimum(self, rhs)
340            }
341
342            #[inline(always)]
343            fn fused_clamp(self, min: Self, max: Self) -> Self {
344                // `minimum(hi, maximum(lo, x))`, the ordinary clamp
345                // composition; the argument order keeps signed-zero ties on
346                // the bounds and propagates NaN from any operand.
347                max.fused_minimum(min.fused_maximum(self))
348            }
349
350            #[inline(always)]
351            fn fused_exp(self) -> Self {
352                self.exp()
353            }
354
355            #[inline(always)]
356            fn fused_log(self) -> Self {
357                self.ln()
358            }
359
360            #[inline(always)]
361            fn fused_sin(self) -> Self {
362                self.sin()
363            }
364
365            #[inline(always)]
366            fn fused_cos(self) -> Self {
367                self.cos()
368            }
369
370            #[inline(always)]
371            fn fused_tanh(self) -> Self {
372                self.tanh()
373            }
374
375            #[inline(always)]
376            fn fused_sqrt(self) -> Self {
377                self.sqrt()
378            }
379
380            #[inline(always)]
381            fn fused_rsqrt(self) -> Self {
382                1.0 / self.sqrt()
383            }
384
385            #[inline(always)]
386            fn fused_pow(self, rhs: Self) -> Self {
387                self.powf(rhs)
388            }
389
390            #[inline(always)]
391            fn fused_expm1(self) -> Self {
392                self.exp_m1()
393            }
394
395            #[inline(always)]
396            fn fused_log1p(self) -> Self {
397                self.ln_1p()
398            }
399        }
400    };
401}
402
403macro_rules! impl_complex_fused_scalar {
404    ($ty:ty, $div:path) => {
405        impl FusedScalar for $ty {
406            #[inline(always)]
407            fn fused_add(self, rhs: Self) -> Self {
408                self + rhs
409            }
410
411            #[inline(always)]
412            fn fused_multiply(self, rhs: Self) -> Self {
413                self * rhs
414            }
415
416            #[inline(always)]
417            fn fused_negate(self) -> Self {
418                -self
419            }
420
421            #[inline(always)]
422            fn fused_conj(self) -> Self {
423                num_complex::Complex::conj(&self)
424            }
425
426            #[inline(always)]
427            fn fused_divide(self, rhs: Self) -> Self {
428                $div(self, rhs)
429            }
430
431            #[inline(always)]
432            fn fused_abs(self) -> Self {
433                Self::new(self.norm(), 0.0)
434            }
435
436            #[inline(always)]
437            fn fused_maximum(self, rhs: Self) -> Self {
438                if self.norm_sqr() >= rhs.norm_sqr() {
439                    self
440                } else {
441                    rhs
442                }
443            }
444
445            #[inline(always)]
446            fn fused_minimum(self, rhs: Self) -> Self {
447                if self.norm_sqr() <= rhs.norm_sqr() {
448                    self
449                } else {
450                    rhs
451                }
452            }
453
454            #[inline(always)]
455            fn fused_clamp(self, min: Self, max: Self) -> Self {
456                self.fused_maximum(min).fused_minimum(max)
457            }
458
459            #[inline(always)]
460            fn fused_exp(self) -> Self {
461                self.exp()
462            }
463
464            #[inline(always)]
465            fn fused_log(self) -> Self {
466                self.ln()
467            }
468
469            #[inline(always)]
470            fn fused_sin(self) -> Self {
471                self.sin()
472            }
473
474            #[inline(always)]
475            fn fused_cos(self) -> Self {
476                self.cos()
477            }
478
479            #[inline(always)]
480            fn fused_tanh(self) -> Self {
481                self.tanh()
482            }
483
484            #[inline(always)]
485            fn fused_sqrt(self) -> Self {
486                self.sqrt()
487            }
488
489            #[inline(always)]
490            fn fused_rsqrt(self) -> Self {
491                Self::new(1.0, 0.0) / self.sqrt()
492            }
493
494            #[inline(always)]
495            fn fused_pow(self, rhs: Self) -> Self {
496                self.powc(rhs)
497            }
498
499            #[inline(always)]
500            fn fused_expm1(self) -> Self {
501                self.exp() - Self::new(1.0, 0.0)
502            }
503
504            #[inline(always)]
505            fn fused_log1p(self) -> Self {
506                (self + Self::new(1.0, 0.0)).ln()
507            }
508        }
509    };
510}
511
512impl_real_fused_scalar!(f32);
513impl_real_fused_scalar!(f64);
514impl_complex_fused_scalar!(
515    num_complex::Complex32,
516    strided_basic::robust_complex_divide_f32
517);
518impl_complex_fused_scalar!(
519    num_complex::Complex64,
520    strided_basic::robust_complex_divide_f64
521);
522
523macro_rules! impl_signed_integer_fused_scalar {
524    ($ty:ty, $label:literal) => {
525        impl FusedScalar for $ty {
526            #[inline]
527            fn fused_dtype_label() -> &'static str {
528                $label
529            }
530
531            #[inline]
532            fn supports_fused_op(op: FusedOp) -> bool {
533                matches!(
534                    op,
535                    FusedOp::Add
536                        | FusedOp::Multiply
537                        | FusedOp::Negate
538                        | FusedOp::Conj
539                        | FusedOp::Abs
540                        | FusedOp::Maximum
541                        | FusedOp::Minimum
542                        | FusedOp::Clamp
543                )
544            }
545
546            #[inline(always)]
547            fn fused_add(self, rhs: Self) -> Self {
548                self.wrapping_add(rhs)
549            }
550
551            #[inline(always)]
552            fn fused_multiply(self, rhs: Self) -> Self {
553                self.wrapping_mul(rhs)
554            }
555
556            #[inline(always)]
557            fn fused_negate(self) -> Self {
558                self.wrapping_neg()
559            }
560
561            #[inline(always)]
562            fn fused_conj(self) -> Self {
563                self
564            }
565
566            #[inline(always)]
567            fn fused_divide(self, _rhs: Self) -> Self {
568                unsupported_fused_op!("divide", $label)
569            }
570
571            #[inline(always)]
572            fn fused_abs(self) -> Self {
573                self.wrapping_abs()
574            }
575
576            #[inline(always)]
577            fn fused_maximum(self, rhs: Self) -> Self {
578                self.max(rhs)
579            }
580
581            #[inline(always)]
582            fn fused_minimum(self, rhs: Self) -> Self {
583                self.min(rhs)
584            }
585
586            #[inline(always)]
587            fn fused_clamp(self, min: Self, max: Self) -> Self {
588                self.fused_maximum(min).fused_minimum(max)
589            }
590
591            #[inline(always)]
592            fn fused_exp(self) -> Self {
593                unsupported_fused_op!("exp", $label)
594            }
595
596            #[inline(always)]
597            fn fused_log(self) -> Self {
598                unsupported_fused_op!("log", $label)
599            }
600
601            #[inline(always)]
602            fn fused_sin(self) -> Self {
603                unsupported_fused_op!("sin", $label)
604            }
605
606            #[inline(always)]
607            fn fused_cos(self) -> Self {
608                unsupported_fused_op!("cos", $label)
609            }
610
611            #[inline(always)]
612            fn fused_tanh(self) -> Self {
613                unsupported_fused_op!("tanh", $label)
614            }
615
616            #[inline(always)]
617            fn fused_sqrt(self) -> Self {
618                unsupported_fused_op!("sqrt", $label)
619            }
620
621            #[inline(always)]
622            fn fused_rsqrt(self) -> Self {
623                unsupported_fused_op!("rsqrt", $label)
624            }
625
626            #[inline(always)]
627            fn fused_pow(self, _rhs: Self) -> Self {
628                unsupported_fused_op!("pow", $label)
629            }
630
631            #[inline(always)]
632            fn fused_expm1(self) -> Self {
633                unsupported_fused_op!("expm1", $label)
634            }
635
636            #[inline(always)]
637            fn fused_log1p(self) -> Self {
638                unsupported_fused_op!("log1p", $label)
639            }
640        }
641    };
642}
643
644impl_signed_integer_fused_scalar!(i32, "i32");
645impl_signed_integer_fused_scalar!(i64, "i64");
646
647impl FusedScalar for bool {
648    #[inline]
649    fn fused_dtype_label() -> &'static str {
650        "bool"
651    }
652
653    #[inline]
654    fn supports_fused_op(op: FusedOp) -> bool {
655        matches!(op, FusedOp::Conj)
656    }
657
658    #[inline(always)]
659    fn fused_add(self, _rhs: Self) -> Self {
660        unsupported_fused_op!("add", "bool")
661    }
662
663    #[inline(always)]
664    fn fused_multiply(self, _rhs: Self) -> Self {
665        unsupported_fused_op!("multiply", "bool")
666    }
667
668    #[inline(always)]
669    fn fused_negate(self) -> Self {
670        unsupported_fused_op!("negate", "bool")
671    }
672
673    #[inline(always)]
674    fn fused_conj(self) -> Self {
675        self
676    }
677
678    #[inline(always)]
679    fn fused_divide(self, _rhs: Self) -> Self {
680        unsupported_fused_op!("divide", "bool")
681    }
682
683    #[inline(always)]
684    fn fused_abs(self) -> Self {
685        unsupported_fused_op!("abs", "bool")
686    }
687
688    #[inline(always)]
689    fn fused_maximum(self, _rhs: Self) -> Self {
690        unsupported_fused_op!("maximum", "bool")
691    }
692
693    #[inline(always)]
694    fn fused_minimum(self, _rhs: Self) -> Self {
695        unsupported_fused_op!("minimum", "bool")
696    }
697
698    #[inline(always)]
699    fn fused_clamp(self, _min: Self, _max: Self) -> Self {
700        unsupported_fused_op!("clamp", "bool")
701    }
702
703    #[inline(always)]
704    fn fused_exp(self) -> Self {
705        unsupported_fused_op!("exp", "bool")
706    }
707
708    #[inline(always)]
709    fn fused_log(self) -> Self {
710        unsupported_fused_op!("log", "bool")
711    }
712
713    #[inline(always)]
714    fn fused_sin(self) -> Self {
715        unsupported_fused_op!("sin", "bool")
716    }
717
718    #[inline(always)]
719    fn fused_cos(self) -> Self {
720        unsupported_fused_op!("cos", "bool")
721    }
722
723    #[inline(always)]
724    fn fused_tanh(self) -> Self {
725        unsupported_fused_op!("tanh", "bool")
726    }
727
728    #[inline(always)]
729    fn fused_sqrt(self) -> Self {
730        unsupported_fused_op!("sqrt", "bool")
731    }
732
733    #[inline(always)]
734    fn fused_rsqrt(self) -> Self {
735        unsupported_fused_op!("rsqrt", "bool")
736    }
737
738    #[inline(always)]
739    fn fused_pow(self, _rhs: Self) -> Self {
740        unsupported_fused_op!("pow", "bool")
741    }
742
743    #[inline(always)]
744    fn fused_expm1(self) -> Self {
745        unsupported_fused_op!("expm1", "bool")
746    }
747
748    #[inline(always)]
749    fn fused_log1p(self) -> Self {
750        unsupported_fused_op!("log1p", "bool")
751    }
752}
753
754#[inline]
755fn op_arity(op: FusedOp) -> usize {
756    match op {
757        FusedOp::Negate
758        | FusedOp::Conj
759        | FusedOp::Abs
760        | FusedOp::Exp
761        | FusedOp::Log
762        | FusedOp::Sin
763        | FusedOp::Cos
764        | FusedOp::Tanh
765        | FusedOp::Sqrt
766        | FusedOp::Rsqrt
767        | FusedOp::Expm1
768        | FusedOp::Log1p => 1,
769        FusedOp::Add
770        | FusedOp::Multiply
771        | FusedOp::Divide
772        | FusedOp::Maximum
773        | FusedOp::Minimum
774        | FusedOp::Pow => 2,
775        FusedOp::Clamp => 3,
776    }
777}
778
779pub(crate) fn validate_plan(
780    plan: &FusedPlan,
781    input_count: usize,
782    output_count: usize,
783) -> Result<()> {
784    if input_count != plan.input_count {
785        return Err(StridedError::RankMismatch(input_count, plan.input_count));
786    }
787    if output_count != plan.outputs.len() {
788        return Err(StridedError::RankMismatch(output_count, plan.outputs.len()));
789    }
790    if output_count == 0 {
791        return Err(StridedError::RankMismatch(0, 1));
792    }
793
794    let mut value_count = plan.input_count;
795    for inst in &plan.ops {
796        let expected_arity = op_arity(inst.op);
797        if inst.inputs.len() != expected_arity {
798            return Err(StridedError::RankMismatch(
799                inst.inputs.len(),
800                expected_arity,
801            ));
802        }
803        for &input in &inst.inputs {
804            if input >= value_count {
805                return Err(StridedError::InvalidAxis {
806                    axis: input,
807                    rank: value_count,
808                });
809            }
810        }
811        value_count += 1;
812    }
813
814    for &output in &plan.outputs {
815        if output >= value_count {
816            return Err(StridedError::InvalidAxis {
817                axis: output,
818                rank: value_count,
819            });
820        }
821    }
822
823    Ok(())
824}
825
826pub(crate) fn validate_plan_for_scalar<T: FusedScalar>(
827    plan: &FusedPlan,
828    input_count: usize,
829    output_count: usize,
830) -> Result<()> {
831    validate_plan(plan, input_count, output_count)?;
832    for inst in &plan.ops {
833        if !T::supports_fused_op(inst.op) {
834            return Err(StridedError::UnsupportedOp {
835                op: inst.op.label(),
836                dtype: T::fused_dtype_label(),
837            });
838        }
839    }
840    Ok(())
841}
842
843fn validate_shapes<T: FusedScalar>(
844    dests: &[StridedViewMut<'_, T>],
845    inputs: &[StridedView<'_, T>],
846) -> Result<ValidatedDestinationLayout> {
847    let dims = dests[0].dims();
848    // Validate the layout once and hand the marker to the specialization, so
849    // the injectivity check does not run twice on the hot single-output path.
850    let first = validate_destination_layout(dests[0].dims(), dests[0].strides())?;
851    for dest in &dests[1..] {
852        validate_destination_layout(dest.dims(), dest.strides())?;
853        ensure_same_shape(dims, dest.dims())?;
854    }
855    for input in inputs {
856        ensure_same_shape(dims, input.dims())?;
857    }
858    Ok(first)
859}
860
861#[inline(always)]
862fn eval_op<T: FusedScalar>(op: FusedOp, regs: &[T], inputs: &[usize]) -> T {
863    match op {
864        FusedOp::Negate
865        | FusedOp::Conj
866        | FusedOp::Abs
867        | FusedOp::Exp
868        | FusedOp::Log
869        | FusedOp::Sin
870        | FusedOp::Cos
871        | FusedOp::Tanh
872        | FusedOp::Sqrt
873        | FusedOp::Rsqrt
874        | FusedOp::Expm1
875        | FusedOp::Log1p => eval_unary(op, regs[inputs[0]]),
876        FusedOp::Add
877        | FusedOp::Multiply
878        | FusedOp::Divide
879        | FusedOp::Maximum
880        | FusedOp::Minimum
881        | FusedOp::Pow => eval_binary(op, regs[inputs[0]], regs[inputs[1]]),
882        FusedOp::Clamp => eval_ternary(op, regs[inputs[0]], regs[inputs[1]], regs[inputs[2]]),
883    }
884}
885
886#[inline(always)]
887fn eval_unary<T: FusedScalar>(op: FusedOp, x: T) -> T {
888    match op {
889        FusedOp::Negate => x.fused_negate(),
890        FusedOp::Conj => x.fused_conj(),
891        FusedOp::Abs => x.fused_abs(),
892        FusedOp::Exp => x.fused_exp(),
893        FusedOp::Log => x.fused_log(),
894        FusedOp::Sin => x.fused_sin(),
895        FusedOp::Cos => x.fused_cos(),
896        FusedOp::Tanh => x.fused_tanh(),
897        FusedOp::Sqrt => x.fused_sqrt(),
898        FusedOp::Rsqrt => x.fused_rsqrt(),
899        FusedOp::Expm1 => x.fused_expm1(),
900        FusedOp::Log1p => x.fused_log1p(),
901        _ => unreachable!("not a unary fused op: {op:?}"),
902    }
903}
904
905#[inline(always)]
906fn eval_binary<T: FusedScalar>(op: FusedOp, a: T, b: T) -> T {
907    match op {
908        FusedOp::Add => a.fused_add(b),
909        FusedOp::Multiply => a.fused_multiply(b),
910        FusedOp::Divide => a.fused_divide(b),
911        FusedOp::Maximum => a.fused_maximum(b),
912        FusedOp::Minimum => a.fused_minimum(b),
913        FusedOp::Pow => a.fused_pow(b),
914        _ => unreachable!("not a binary fused op: {op:?}"),
915    }
916}
917
918#[inline(always)]
919fn eval_ternary<T: FusedScalar>(op: FusedOp, a: T, b: T, c: T) -> T {
920    match op {
921        FusedOp::Clamp => a.fused_clamp(b, c),
922        _ => unreachable!("not a ternary fused op: {op:?}"),
923    }
924}
925
926#[derive(Clone, Copy)]
927enum StaticFusedKind {
928    Unary(FusedOp, usize),
929    Binary(FusedOp, usize, usize),
930    Ternary(FusedOp, usize, usize, usize),
931    AddMulLeft,
932    AddMulRight,
933    MulAddExp,
934    DivClampSqrtRsqrt,
935}
936
937#[cfg(test)]
938std::thread_local! {
939    static UNINITIALIZED_STATIC_FAMILY_HITS: core::cell::Cell<[usize; 7]> =
940        const { core::cell::Cell::new([0; 7]) };
941}
942
943#[cfg(test)]
944impl StaticFusedKind {
945    fn test_index(self) -> usize {
946        match self {
947            Self::Unary(..) => 0,
948            Self::Binary(..) => 1,
949            Self::Ternary(..) => 2,
950            Self::AddMulLeft => 3,
951            Self::AddMulRight => 4,
952            Self::MulAddExp => 5,
953            Self::DivClampSqrtRsqrt => 6,
954        }
955    }
956}
957
958#[cfg(test)]
959fn reset_uninitialized_static_family_hits() {
960    UNINITIALIZED_STATIC_FAMILY_HITS.set([0; 7]);
961}
962
963#[cfg(test)]
964fn uninitialized_static_family_hits() -> [usize; 7] {
965    UNINITIALIZED_STATIC_FAMILY_HITS.get()
966}
967
968#[cfg(test)]
969fn record_uninitialized_static_family_hit(kind: StaticFusedKind) {
970    UNINITIALIZED_STATIC_FAMILY_HITS.set({
971        let mut hits = UNINITIALIZED_STATIC_FAMILY_HITS.get();
972        hits[kind.test_index()] += 1;
973        hits
974    });
975}
976
977fn classify_static_specialization(plan: &FusedPlan) -> Option<StaticFusedKind> {
978    if plan.outputs.len() != 1 {
979        return None;
980    }
981    if let [inst] = plan.ops.as_slice() {
982        if plan.outputs[0] != plan.input_count {
983            return None;
984        }
985        return match (op_arity(inst.op), inst.inputs.as_slice()) {
986            (1, [a]) => Some(StaticFusedKind::Unary(inst.op, *a)),
987            (2, [a, b]) => Some(StaticFusedKind::Binary(inst.op, *a, *b)),
988            (3, [a, b, c]) => Some(StaticFusedKind::Ternary(inst.op, *a, *b, *c)),
989            _ => None,
990        };
991    }
992    if plan.input_count == 2
993        && plan.outputs.as_slice() == [3]
994        && plan.ops.len() == 2
995        && plan.ops[0].op == FusedOp::Add
996        && plan.ops[0].inputs.as_slice() == [0, 1]
997        && plan.ops[1].op == FusedOp::Multiply
998    {
999        return match plan.ops[1].inputs.as_slice() {
1000            [2, 0] => Some(StaticFusedKind::AddMulLeft),
1001            [0, 2] => Some(StaticFusedKind::AddMulRight),
1002            _ => None,
1003        };
1004    }
1005    if plan.input_count == 3
1006        && plan.outputs.as_slice() == [5]
1007        && plan.ops.len() == 3
1008        && plan.ops[0].op == FusedOp::Multiply
1009        && plan.ops[0].inputs.as_slice() == [0, 1]
1010        && plan.ops[1].op == FusedOp::Add
1011        && plan.ops[1].inputs.as_slice() == [3, 2]
1012        && plan.ops[2].op == FusedOp::Exp
1013        && plan.ops[2].inputs.as_slice() == [4]
1014    {
1015        return Some(StaticFusedKind::MulAddExp);
1016    }
1017    if plan.input_count == 4
1018        && plan.outputs.as_slice() == [8]
1019        && plan.ops.len() == 5
1020        && plan.ops[0].op == FusedOp::Divide
1021        && plan.ops[0].inputs.as_slice() == [0, 1]
1022        && plan.ops[1].op == FusedOp::Maximum
1023        && plan.ops[1].inputs.as_slice() == [4, 2]
1024        && plan.ops[2].op == FusedOp::Minimum
1025        && plan.ops[2].inputs.as_slice() == [5, 3]
1026        && plan.ops[3].op == FusedOp::Sqrt
1027        && plan.ops[3].inputs.as_slice() == [6]
1028        && plan.ops[4].op == FusedOp::Rsqrt
1029        && plan.ops[4].inputs.as_slice() == [7]
1030    {
1031        return Some(StaticFusedKind::DivClampSqrtRsqrt);
1032    }
1033    None
1034}
1035
1036trait StaticOutput<T: FusedScalar> {
1037    type Value: Copy + Send + Sync;
1038
1039    #[cfg(test)]
1040    const IS_UNINITIALIZED: bool;
1041
1042    fn write(value: T) -> Self::Value;
1043}
1044
1045struct InitializedStaticOutput;
1046
1047impl<T: FusedScalar> StaticOutput<T> for InitializedStaticOutput {
1048    type Value = T;
1049
1050    #[cfg(test)]
1051    const IS_UNINITIALIZED: bool = false;
1052
1053    #[inline(always)]
1054    fn write(value: T) -> T {
1055        value
1056    }
1057}
1058
1059struct UninitializedStaticOutput;
1060
1061impl<T: FusedScalar> StaticOutput<T> for UninitializedStaticOutput {
1062    type Value = MaybeUninit<T>;
1063
1064    #[cfg(test)]
1065    const IS_UNINITIALIZED: bool = true;
1066
1067    #[inline(always)]
1068    fn write(value: T) -> MaybeUninit<T> {
1069        MaybeUninit::new(value)
1070    }
1071}
1072
1073fn try_static_specialization_validated<T, O>(
1074    dest: &mut StridedViewMut<'_, O::Value>,
1075    inputs: &[StridedView<'_, T>],
1076    plan: &FusedPlan,
1077    validated: ValidatedDestinationLayout,
1078    exec: Exec<'_>,
1079) -> Result<bool>
1080where
1081    T: FusedScalar,
1082    O: StaticOutput<T>,
1083{
1084    let Some(kind) = classify_static_specialization(plan) else {
1085        return Ok(false);
1086    };
1087    #[cfg(test)]
1088    if O::IS_UNINITIALIZED {
1089        record_uninitialized_static_family_hit(kind);
1090    }
1091
1092    match kind {
1093        StaticFusedKind::Unary(op, a) => {
1094            // Dispatch on the op once, outside the element loop, so the
1095            // specialization is monomorphized per `FusedOp` and vectorizes
1096            // (tensor4all/strided-rs#305 item 2).
1097            macro_rules! unary {
1098                ($apply:expr) => {{
1099                    // SAFETY: matching shapes and this destination layout were
1100                    // validated before specialization/replay.
1101                    unsafe {
1102                        map_into_validated(
1103                            dest,
1104                            &inputs[a],
1105                            strided_basic::Classed::new(op_class(op), |x: T| O::write($apply(x))),
1106                            validated,
1107                            exec,
1108                        )
1109                    }?
1110                }};
1111            }
1112            match op {
1113                FusedOp::Negate => unary!(|x: T| x.fused_negate()),
1114                FusedOp::Conj => unary!(|x: T| x.fused_conj()),
1115                FusedOp::Abs => unary!(|x: T| x.fused_abs()),
1116                FusedOp::Exp => unary!(|x: T| x.fused_exp()),
1117                FusedOp::Log => unary!(|x: T| x.fused_log()),
1118                FusedOp::Sin => unary!(|x: T| x.fused_sin()),
1119                FusedOp::Cos => unary!(|x: T| x.fused_cos()),
1120                FusedOp::Tanh => unary!(|x: T| x.fused_tanh()),
1121                FusedOp::Sqrt => unary!(|x: T| x.fused_sqrt()),
1122                FusedOp::Rsqrt => unary!(|x: T| x.fused_rsqrt()),
1123                FusedOp::Expm1 => unary!(|x: T| x.fused_expm1()),
1124                FusedOp::Log1p => unary!(|x: T| x.fused_log1p()),
1125                _ => unreachable!("not a unary fused op: {op:?}"),
1126            }
1127        }
1128        StaticFusedKind::Binary(op, a, b) => {
1129            // As above: one monomorphized kernel per `FusedOp`.
1130            macro_rules! binary {
1131                ($apply:expr) => {{
1132                    // SAFETY: matching shapes and this destination layout were
1133                    // validated before specialization/replay.
1134                    unsafe {
1135                        zip_map2_into_validated(
1136                            dest,
1137                            &inputs[a],
1138                            &inputs[b],
1139                            |x, y| O::write($apply(x, y)),
1140                            validated,
1141                            exec,
1142                        )
1143                    }?
1144                }};
1145            }
1146            match op {
1147                FusedOp::Add => binary!(|x: T, y: T| x.fused_add(y)),
1148                FusedOp::Multiply => binary!(|x: T, y: T| x.fused_multiply(y)),
1149                FusedOp::Divide => binary!(|x: T, y: T| x.fused_divide(y)),
1150                FusedOp::Maximum => binary!(|x: T, y: T| x.fused_maximum(y)),
1151                FusedOp::Minimum => binary!(|x: T, y: T| x.fused_minimum(y)),
1152                FusedOp::Pow => binary!(|x: T, y: T| x.fused_pow(y)),
1153                _ => unreachable!("not a binary fused op: {op:?}"),
1154            }
1155        }
1156        StaticFusedKind::Ternary(op, a, b, c) => match op {
1157            FusedOp::Clamp => {
1158                // SAFETY: matching shapes and this destination layout were
1159                // validated before specialization/replay.
1160                unsafe {
1161                    zip_map3_into_validated(
1162                        dest,
1163                        &inputs[a],
1164                        &inputs[b],
1165                        &inputs[c],
1166                        |x, y, z| O::write(x.fused_clamp(y, z)),
1167                        validated,
1168                        exec,
1169                    )
1170                }?
1171            }
1172            _ => unreachable!("not a ternary fused op: {op:?}"),
1173        },
1174        // SAFETY: matching shapes and this destination layout were validated before specialization/replay.
1175        StaticFusedKind::AddMulLeft => unsafe {
1176            zip_map2_into_validated(
1177                dest,
1178                &inputs[0],
1179                &inputs[1],
1180                |a, b| O::write(a.fused_add(b).fused_multiply(a)),
1181                validated,
1182                exec,
1183            )
1184        }?,
1185        // SAFETY: matching shapes and this destination layout were validated before specialization/replay.
1186        StaticFusedKind::AddMulRight => unsafe {
1187            zip_map2_into_validated(
1188                dest,
1189                &inputs[0],
1190                &inputs[1],
1191                |a, b| O::write(a.fused_multiply(a.fused_add(b))),
1192                validated,
1193                exec,
1194            )
1195        }?,
1196        // SAFETY: matching shapes and this destination layout were validated before specialization/replay.
1197        StaticFusedKind::MulAddExp => unsafe {
1198            zip_map3_into_validated(
1199                dest,
1200                &inputs[0],
1201                &inputs[1],
1202                &inputs[2],
1203                |a, b, c| O::write(a.fused_multiply(b).fused_add(c).fused_exp()),
1204                validated,
1205                exec,
1206            )
1207        }?,
1208        // SAFETY: matching shapes and this destination layout were validated before specialization/replay.
1209        StaticFusedKind::DivClampSqrtRsqrt => unsafe {
1210            zip_map4_into_validated(
1211                dest,
1212                &inputs[0],
1213                &inputs[1],
1214                &inputs[2],
1215                &inputs[3],
1216                |a, b, lo, hi| {
1217                    O::write(
1218                        a.fused_divide(b)
1219                            .fused_maximum(lo)
1220                            .fused_minimum(hi)
1221                            .fused_sqrt()
1222                            .fused_rsqrt(),
1223                    )
1224                },
1225                validated,
1226                exec,
1227            )
1228        }?,
1229    }
1230    Ok(true)
1231}
1232
1233/// Take the initialized specialization with an already validated destination
1234/// layout, so the caller does not run the injectivity check twice.
1235fn try_static_specialization_initialized<T: FusedScalar>(
1236    dests: &mut [StridedViewMut<'_, T>],
1237    inputs: &[StridedView<'_, T>],
1238    plan: &FusedPlan,
1239    validated: ValidatedDestinationLayout,
1240    exec: Exec<'_>,
1241) -> Result<bool> {
1242    if dests.len() != 1 {
1243        return Ok(false);
1244    }
1245    try_static_specialization_validated::<T, InitializedStaticOutput>(
1246        &mut dests[0],
1247        inputs,
1248        plan,
1249        validated,
1250        exec,
1251    )
1252}
1253
1254/// Test-only thin wrapper over `validate_destination_layout_without_alloc` plus
1255/// [`try_static_specialization_initialized`], matching the helper the
1256/// specialization tests used before the layout marker was threaded through.
1257#[cfg(test)]
1258fn try_static_specialization<T: FusedScalar>(
1259    dests: &mut [StridedViewMut<'_, T>],
1260    inputs: &[StridedView<'_, T>],
1261    plan: &FusedPlan,
1262) -> Result<bool> {
1263    if dests.len() != 1 {
1264        return Ok(false);
1265    }
1266    let validated = strided_basic::execution::validate_destination_layout_without_alloc(
1267        dests[0].dims(),
1268        dests[0].strides(),
1269    )?;
1270    try_static_specialization_initialized(dests, inputs, plan, validated, Exec::serial())
1271}
1272
1273/// Registers the scalar interpreter keeps inline; a larger program spills to
1274/// the heap (#306 item 6).
1275const INLINE_REGS: usize = 12;
1276
1277unsafe fn interpret_inner_loop<T: FusedScalar>(
1278    dst_ptrs: &[*mut T],
1279    input_ptrs: &[*const T],
1280    plan: &FusedPlan,
1281    offsets: &[isize],
1282    len: usize,
1283    strides: &[isize],
1284) {
1285    let register_count = plan.input_count + plan.ops.len();
1286    if len == 0 || register_count == 0 {
1287        return;
1288    }
1289    // SAFETY: a validated plan with instructions has at least one input, and
1290    // the enclosing kernel proved every operand addresses a readable element.
1291    let init = unsafe { *input_ptrs[0].offset(offsets[dst_ptrs.len()]) };
1292    if register_count <= INLINE_REGS {
1293        let mut inline = [init; INLINE_REGS];
1294        // SAFETY: forwarded contract.
1295        unsafe {
1296            interpret_loop_body(
1297                dst_ptrs,
1298                input_ptrs,
1299                plan,
1300                offsets,
1301                len,
1302                strides,
1303                &mut inline[..register_count],
1304            )
1305        };
1306    } else {
1307        let mut heap = vec![init; register_count];
1308        // SAFETY: forwarded contract.
1309        unsafe {
1310            interpret_loop_body(dst_ptrs, input_ptrs, plan, offsets, len, strides, &mut heap)
1311        };
1312    }
1313}
1314
1315/// One interpreter pass over a block, writing the register file by index so a
1316/// stack or heap file is used identically.
1317///
1318/// # Safety
1319/// As [`interpret_inner_loop`].
1320#[allow(clippy::too_many_arguments)]
1321unsafe fn interpret_loop_body<T: FusedScalar>(
1322    dst_ptrs: &[*mut T],
1323    input_ptrs: &[*const T],
1324    plan: &FusedPlan,
1325    offsets: &[isize],
1326    len: usize,
1327    strides: &[isize],
1328    regs: &mut [T],
1329) {
1330    let output_count = dst_ptrs.len();
1331    for i in 0..len {
1332        let i = i as isize;
1333        for (input_index, &input_ptr) in input_ptrs.iter().enumerate() {
1334            let stride_index = output_count + input_index;
1335            let stride = inner_stride(strides, stride_index);
1336            // SAFETY: forwarded caller contract.
1337            regs[input_index] = unsafe { *input_ptr.offset(offsets[stride_index] + i * stride) };
1338        }
1339        for (instruction, inst) in plan.ops.iter().enumerate() {
1340            let output = plan.input_count + instruction;
1341            regs[output] = eval_op(inst.op, &regs[..output], &inst.inputs);
1342        }
1343        for (output_index, &dst_ptr) in dst_ptrs.iter().enumerate() {
1344            let stride = inner_stride(strides, output_index);
1345            // SAFETY: forwarded caller contract.
1346            unsafe {
1347                *dst_ptr.offset(offsets[output_index] + i * stride) =
1348                    regs[plan.outputs[output_index]];
1349            }
1350        }
1351    }
1352}
1353
1354/// Seed for the chunk register file, or `None` when this scalar has no vector
1355/// kernels (or there is no input to seed from).
1356#[inline]
1357fn chunk_seed<T: FusedScalar>(inputs: &[StridedView<'_, T>]) -> Option<T> {
1358    if inputs.is_empty() || !chunk::is_real_chunk_scalar::<T>() {
1359        return None;
1360    }
1361    // SAFETY: callers reach this with a non-empty destination, and every input
1362    // shares the destination shape, so the first input has a readable element.
1363    Some(unsafe { *inputs[0].ptr() })
1364}
1365
1366/// Register file for `seed`, or an empty one when the chunked path is declined.
1367#[inline]
1368fn register_file<T: FusedScalar, R>(
1369    plan: &FusedPlan,
1370    seed: Option<T>,
1371    f: impl FnOnce(&mut [T]) -> R,
1372) -> R {
1373    chunk::with_register_file(plan, seed, f)
1374}
1375
1376/// Inputs the chunked path reads straight from the operand, empty when it is
1377/// declined.
1378///
1379/// A stride-0 input is never direct: the register machine reads the broadcast
1380/// value from its pre-filled register chunk, not from `offset + done`. The
1381/// innermost stride of each operand (outputs first, then inputs) is read from
1382/// `ordered_strides[i].first()`.
1383#[inline]
1384fn direct_inputs<T: FusedScalar, S: AsRef<[isize]>>(
1385    plan: &FusedPlan,
1386    seed: Option<T>,
1387    ordered_strides: &[S],
1388    output_count: usize,
1389) -> strided_basic::OperandVec<bool> {
1390    match seed {
1391        Some(_) => {
1392            let mut direct = chunk::single_use_inputs(plan);
1393            for (index, flag) in direct.iter_mut().enumerate() {
1394                let stride = ordered_strides
1395                    .get(output_count + index)
1396                    .and_then(|strides| strides.as_ref().first())
1397                    .copied()
1398                    .unwrap_or(0);
1399                if stride != 1 {
1400                    *flag = false;
1401                }
1402            }
1403            direct
1404        }
1405        None => strided_basic::OperandVec::new(),
1406    }
1407}
1408
1409/// Stride of `index` along the block's innermost axis.
1410///
1411/// A rank-zero block carries no strides: its single element sits at the block
1412/// offset, which is what a stride of 0 yields.
1413#[inline]
1414fn inner_stride(strides: &[isize], index: usize) -> isize {
1415    strides.get(index).copied().unwrap_or(0)
1416}
1417
1418fn interpret_fused_elementwise_into<T: FusedScalar>(
1419    dests: &mut [StridedViewMut<'_, T>],
1420    inputs: &[StridedView<'_, T>],
1421    plan: &FusedPlan,
1422    exec: Exec<'_>,
1423) -> Result<()> {
1424    if dests[0].is_empty() {
1425        return Ok(());
1426    }
1427    let dims: strided_basic::InlineVec<usize> = dests[0].dims().iter().copied().collect();
1428
1429    let dst_ptrs: strided_basic::OperandVec<OperandPtr<T>> = dests
1430        .iter_mut()
1431        .map(|dest| OperandPtr::new(dest.as_mut_ptr()))
1432        .collect();
1433    let input_ptrs: strided_basic::OperandVec<OperandPtr<T>> = inputs
1434        .iter()
1435        .map(|input| OperandPtr::new(input.ptr() as *mut T))
1436        .collect();
1437
1438    let mut strides_list: strided_basic::OperandVec<&[isize]> =
1439        strided_basic::OperandVec::with_capacity(dests.len() + inputs.len());
1440    for dest in dests.iter() {
1441        strides_list.push(dest.strides());
1442    }
1443    for input in inputs {
1444        strides_list.push(input.strides());
1445    }
1446
1447    let elem_size = std::mem::size_of::<T>();
1448    let total = dests[0].len();
1449    let (fused_dims, ordered_strides, kernel_plan) = if total <= SMALL_TENSOR_THRESHOLD {
1450        // SAFETY: the enclosing checked kernel supplies validated layout metadata and its derived partitions.
1451        unsafe { build_plan_fused_small(&dims, &strides_list) }
1452    } else {
1453        // SAFETY: the enclosing checked kernel supplies validated layout metadata and its derived partitions.
1454        unsafe { build_plan_fused(&dims, &strides_list, Some(0), elem_size) }
1455    };
1456
1457    let initial_offsets: strided_basic::OperandVec<isize> =
1458        ordered_strides.iter().map(|_| 0isize).collect();
1459    let seed = chunk_seed(inputs);
1460    let operands = dst_ptrs.len() + input_ptrs.len();
1461    let run_partition = |dims: &[usize],
1462                         blocks: &[usize],
1463                         strides_list: &[&[isize]],
1464                         offsets: &[isize]|
1465     -> Result<()> {
1466        let direct = direct_inputs(plan, seed, strides_list, dst_ptrs.len());
1467        register_file(plan, seed, |regs| {
1468            let run_block = |offsets: &[isize], len: usize, strides: &[isize]| -> Result<()> {
1469                if seed.is_some() && chunk::block_replay_eligible(strides, dst_ptrs.len(), operands)
1470                {
1471                    // SAFETY: validated shapes/layouts and the derived block bound every access.
1472                    unsafe {
1473                        chunk::replay_chunked(
1474                            operand_ptrs(&dst_ptrs),
1475                            operand_const_ptrs(&input_ptrs),
1476                            plan,
1477                            offsets,
1478                            len,
1479                            strides,
1480                            &direct,
1481                            regs,
1482                        );
1483                    }
1484                    return Ok(());
1485                }
1486                // SAFETY: validated shapes/layouts and the derived block bound every access.
1487                unsafe {
1488                    interpret_inner_loop(
1489                        operand_ptrs(&dst_ptrs),
1490                        operand_const_ptrs(&input_ptrs),
1491                        plan,
1492                        offsets,
1493                        len,
1494                        strides,
1495                    );
1496                }
1497                Ok(())
1498            };
1499            // SAFETY: the enclosing checked kernel supplies validated layout metadata and its derived partitions.
1500            unsafe {
1501                for_each_inner_block_preordered(dims, blocks, strides_list, offsets, run_block)
1502            }
1503        })
1504    };
1505    replay_partitions(
1506        &fused_dims,
1507        &kernel_plan.block,
1508        &ordered_strides,
1509        &initial_offsets,
1510        plan_class(plan),
1511        exec,
1512        &run_partition,
1513    )
1514}
1515
1516/// Serial cost class of a fused program: `Transcendental` when any instruction is
1517/// a libm-like function, `Map` for arithmetic chains (see
1518/// `strided_basic::execution::KernelClass`).
1519fn plan_class(plan: &FusedPlan) -> KernelClass {
1520    if plan
1521        .ops
1522        .iter()
1523        .any(|inst| op_class(inst.op) == KernelClass::Transcendental)
1524    {
1525        KernelClass::Transcendental
1526    } else {
1527        KernelClass::Map
1528    }
1529}
1530
1531/// Serial cost class of one fused instruction: `Transcendental` for a libm-like
1532/// function, `Map` otherwise.
1533fn op_class(op: FusedOp) -> KernelClass {
1534    if matches!(
1535        op,
1536        FusedOp::Exp
1537            | FusedOp::Log
1538            | FusedOp::Sin
1539            | FusedOp::Cos
1540            | FusedOp::Tanh
1541            | FusedOp::Pow
1542            | FusedOp::Expm1
1543            | FusedOp::Log1p
1544    ) {
1545        KernelClass::Transcendental
1546    } else {
1547        KernelClass::Map
1548    }
1549}
1550
1551/// Run `run_partition` over the whole region on the calling thread, or split
1552/// the region across the budget of `exec` once a fork pays for it.
1553fn replay_partitions<S: AsRef<[isize]>, F>(
1554    dims: &[usize],
1555    blocks: &[usize],
1556    ordered_strides: &[S],
1557    initial_offsets: &[isize],
1558    class: KernelClass,
1559    exec: Exec<'_>,
1560    run_partition: &F,
1561) -> Result<()>
1562where
1563    F: Fn(&[usize], &[usize], &[&[isize]], &[isize]) -> Result<()> + Sync,
1564{
1565    let total: usize = dims.iter().product();
1566    if parallel_width(&exec, class, total).get() > 1 {
1567        // SAFETY: the enclosing checked kernel supplies validated layout metadata and its derived partitions.
1568        let costs = unsafe { compute_costs(ordered_strides) };
1569        // SAFETY: the enclosing checked kernel supplies validated layout metadata and its derived partitions.
1570        return unsafe {
1571            mapreduce_threaded(
1572                dims,
1573                blocks,
1574                ordered_strides,
1575                initial_offsets,
1576                &costs,
1577                class,
1578                exec,
1579                run_partition,
1580            )
1581        };
1582    }
1583    let strides_refs: strided_basic::OperandVec<&[isize]> =
1584        ordered_strides.iter().map(|s| s.as_ref()).collect();
1585    run_partition(dims, blocks, &strides_refs, initial_offsets)
1586}
1587
1588unsafe fn interpret_inner_loop_uninit<T: FusedScalar>(
1589    dst_ptrs: &[*mut MaybeUninit<T>],
1590    input_ptrs: &[*const T],
1591    plan: &FusedPlan,
1592    offsets: &[isize],
1593    len: usize,
1594    strides: &[isize],
1595) {
1596    let register_count = plan.input_count + plan.ops.len();
1597    if len == 0 || register_count == 0 {
1598        return;
1599    }
1600    // SAFETY: a validated plan with instructions has at least one input, and
1601    // the enclosing kernel proved every operand addresses a readable element.
1602    let init = unsafe { *input_ptrs[0].offset(offsets[dst_ptrs.len()]) };
1603    if register_count <= INLINE_REGS {
1604        let mut inline = [init; INLINE_REGS];
1605        // SAFETY: forwarded contract.
1606        unsafe {
1607            interpret_loop_body_uninit(
1608                dst_ptrs,
1609                input_ptrs,
1610                plan,
1611                offsets,
1612                len,
1613                strides,
1614                &mut inline[..register_count],
1615            )
1616        };
1617    } else {
1618        let mut heap = vec![init; register_count];
1619        // SAFETY: forwarded contract.
1620        unsafe {
1621            interpret_loop_body_uninit(dst_ptrs, input_ptrs, plan, offsets, len, strides, &mut heap)
1622        };
1623    }
1624}
1625
1626/// Uninitialized-destination sibling of [`interpret_loop_body`].
1627///
1628/// # Safety
1629/// As [`interpret_inner_loop_uninit`].
1630#[allow(clippy::too_many_arguments)]
1631unsafe fn interpret_loop_body_uninit<T: FusedScalar>(
1632    dst_ptrs: &[*mut MaybeUninit<T>],
1633    input_ptrs: &[*const T],
1634    plan: &FusedPlan,
1635    offsets: &[isize],
1636    len: usize,
1637    strides: &[isize],
1638    regs: &mut [T],
1639) {
1640    let output_count = dst_ptrs.len();
1641    for i in 0..len {
1642        let i = i as isize;
1643        for (input_index, &input_ptr) in input_ptrs.iter().enumerate() {
1644            let stride_index = output_count + input_index;
1645            let stride = inner_stride(strides, stride_index);
1646            // SAFETY: forwarded caller contract.
1647            regs[input_index] = unsafe { *input_ptr.offset(offsets[stride_index] + i * stride) };
1648        }
1649        for (instruction, inst) in plan.ops.iter().enumerate() {
1650            let output = plan.input_count + instruction;
1651            regs[output] = eval_op(inst.op, &regs[..output], &inst.inputs);
1652        }
1653        for (output_index, &dst_ptr) in dst_ptrs.iter().enumerate() {
1654            let stride = inner_stride(strides, output_index);
1655            // SAFETY: forwarded caller contract.
1656            unsafe {
1657                *dst_ptr.offset(offsets[output_index] + i * stride) =
1658                    MaybeUninit::new(regs[plan.outputs[output_index]]);
1659            }
1660        }
1661    }
1662}
1663
1664/// Single-destination front end for [`fused_elementwise_into_uninit_multi`].
1665#[cfg(test)]
1666pub(crate) fn fused_elementwise_into_uninit<T: FusedScalar>(
1667    dest: &mut StridedViewMut<'_, MaybeUninit<T>>,
1668    inputs: &[StridedView<'_, T>],
1669    plan: &FusedPlan,
1670    validated: ValidatedDestinationLayout,
1671    exec: Exec<'_>,
1672) -> Result<()> {
1673    fused_elementwise_into_uninit_multi(
1674        std::slice::from_mut(dest),
1675        inputs,
1676        plan,
1677        std::slice::from_ref(&validated),
1678        exec,
1679    )
1680}
1681
1682/// Evaluate a plan into one or more fully-overwritten uninitialized destinations.
1683///
1684/// `validated[i]` must come from `validate_destination_layout_without_alloc`
1685/// for `dests[i]`'s layout. A single destination keeps the static kernel
1686/// specialization; several destinations always take the interpreter or the
1687/// chunked register machine.
1688pub(crate) fn fused_elementwise_into_uninit_multi<T: FusedScalar>(
1689    dests: &mut [StridedViewMut<'_, MaybeUninit<T>>],
1690    inputs: &[StridedView<'_, T>],
1691    plan: &FusedPlan,
1692    validated: &[ValidatedDestinationLayout],
1693    mut exec: Exec<'_>,
1694) -> Result<()> {
1695    validate_plan_for_scalar::<T>(plan, inputs.len(), dests.len())?;
1696    let dims = dests[0].dims();
1697    for dest in dests.iter() {
1698        ensure_same_shape(dims, dest.dims())?;
1699    }
1700    for input in inputs {
1701        ensure_same_shape(dims, input.dims())?;
1702    }
1703
1704    if dests.len() == 1
1705        && try_static_specialization_validated::<T, UninitializedStaticOutput>(
1706            &mut dests[0],
1707            inputs,
1708            plan,
1709            validated[0],
1710            exec.reborrow(),
1711        )?
1712    {
1713        return Ok(());
1714    }
1715
1716    if dests[0].is_empty() {
1717        return Ok(());
1718    }
1719    interpret_fused_elementwise_into_uninit(dests, inputs, plan, exec)
1720}
1721
1722fn interpret_fused_elementwise_into_uninit<T: FusedScalar>(
1723    dests: &mut [StridedViewMut<'_, MaybeUninit<T>>],
1724    inputs: &[StridedView<'_, T>],
1725    plan: &FusedPlan,
1726    exec: Exec<'_>,
1727) -> Result<()> {
1728    let dims: strided_basic::InlineVec<usize> = dests[0].dims().iter().copied().collect();
1729    let dst_ptrs: strided_basic::OperandVec<OperandPtr<MaybeUninit<T>>> = dests
1730        .iter_mut()
1731        .map(|dest| OperandPtr::new(dest.as_mut_ptr()))
1732        .collect();
1733    let input_ptrs: strided_basic::OperandVec<OperandPtr<T>> = inputs
1734        .iter()
1735        .map(|input| OperandPtr::new(input.ptr() as *mut T))
1736        .collect();
1737    let mut strides_list: strided_basic::OperandVec<&[isize]> =
1738        strided_basic::OperandVec::with_capacity(dests.len() + inputs.len());
1739    for dest in dests.iter() {
1740        strides_list.push(dest.strides());
1741    }
1742    for input in inputs {
1743        strides_list.push(input.strides());
1744    }
1745    let total = dests[0].len();
1746    let (fused_dims, ordered_strides, kernel_plan) = if total <= SMALL_TENSOR_THRESHOLD {
1747        // SAFETY: the enclosing checked kernel supplies validated layout metadata and its derived partitions.
1748        unsafe { build_plan_fused_small(&dims, &strides_list) }
1749    } else {
1750        // SAFETY: the enclosing checked kernel supplies validated layout metadata and its derived partitions.
1751        unsafe { build_plan_fused(&dims, &strides_list, Some(0), core::mem::size_of::<T>()) }
1752    };
1753
1754    let initial_offsets: strided_basic::OperandVec<isize> =
1755        ordered_strides.iter().map(|_| 0isize).collect();
1756    let seed = chunk_seed(inputs);
1757    let operands = dst_ptrs.len() + input_ptrs.len();
1758    let run_partition = |dims: &[usize],
1759                         blocks: &[usize],
1760                         strides_list: &[&[isize]],
1761                         offsets: &[isize]|
1762     -> Result<()> {
1763        let direct = direct_inputs(plan, seed, strides_list, dst_ptrs.len());
1764        register_file(plan, seed, |regs| {
1765            let run_block = |offsets: &[isize], len: usize, strides: &[isize]| -> Result<()> {
1766                if seed.is_some() && chunk::block_replay_eligible(strides, dst_ptrs.len(), operands)
1767                {
1768                    // SAFETY: validated shapes/layouts and the derived block bound every access.
1769                    unsafe {
1770                        chunk::replay_chunked_uninit(
1771                            operand_ptrs(&dst_ptrs),
1772                            operand_const_ptrs(&input_ptrs),
1773                            plan,
1774                            offsets,
1775                            len,
1776                            strides,
1777                            &direct,
1778                            regs,
1779                        );
1780                    }
1781                    return Ok(());
1782                }
1783                // SAFETY: validated shapes/layouts and the derived block bound every access.
1784                unsafe {
1785                    interpret_inner_loop_uninit(
1786                        operand_ptrs(&dst_ptrs),
1787                        operand_const_ptrs(&input_ptrs),
1788                        plan,
1789                        offsets,
1790                        len,
1791                        strides,
1792                    );
1793                }
1794                Ok(())
1795            };
1796            // SAFETY: the enclosing checked kernel supplies validated layout metadata and its derived partitions.
1797            unsafe {
1798                for_each_inner_block_preordered(dims, blocks, strides_list, offsets, run_block)
1799            }
1800        })
1801    };
1802    replay_partitions(
1803        &fused_dims,
1804        &kernel_plan.block,
1805        &ordered_strides,
1806        &initial_offsets,
1807        plan_class(plan),
1808        exec,
1809        &run_partition,
1810    )
1811}
1812
1813/// Evaluate a runtime-DAG elementwise plan into one or more destinations.
1814///
1815/// The plan is validated before any destination is written:
1816///
1817/// - `inputs.len()` must equal `plan.input_count`;
1818/// - `dests.len()` must equal `plan.outputs.len()`;
1819/// - instruction operands must reference earlier SSA values with the right
1820///   arity for their [`FusedOp`];
1821/// - every input and destination must have exactly the destination shape;
1822/// - each mutable destination layout must be injective, so two logical output
1823///   elements never map to the same memory address.
1824///
1825/// The implementation dispatches known single-output plans to existing static
1826/// `map_into`/`zip_map*_into` kernels, replays a block that is contiguous for
1827/// every operand through a chunked register machine, and falls back to a
1828/// per-element interpreter for arbitrary validated DAGs. Overlapping
1829/// source/destination memory is not supported by the strided kernels generally.
1830///
1831/// # Numerics
1832///
1833/// The chunked replay evaluates `Exp`, `Log`, `Sin`, `Cos`, `Tanh` and `Expm1`
1834/// with the `elem-simd-math` slice kernels — the same kernels a per-instruction
1835/// dispatch through [`strided_basic::map_into_uninit_with_kernel`] uses, so the
1836/// fused and the non-fused route agree with each other. Against `std`'s scalar
1837/// functions those six instructions differ by at most one ULP of their own
1838/// output magnitude; a chain of them multiplies that difference by the output
1839/// magnitude, so an exponential chain is not bit-stable to `std`. `Log1p` and
1840/// every other instruction (`Add`, `Multiply`, `Negate`, `Conj`, `Divide`,
1841/// `Abs`, `Maximum`, `Minimum`, `Clamp`, `Sqrt`, `Rsqrt`, `Pow`) keep the scalar
1842/// `FusedScalar` methods and are bit-identical to the per-element replay.
1843///
1844/// Real `Maximum`, `Minimum`, and `Clamp` share the NaN-propagating select and
1845/// signed-zero contract of `strided_basic::nan_propagating_maximum` /
1846/// `nan_propagating_minimum` (issue #315): a NaN in any operand makes the
1847/// result NaN, and a tie keeps the left operand, so `maximum(-0.0, 0.0)` and
1848/// `clamp` on equal bounds preserve the signed zero of the left/bound operand.
1849/// Complex `Abs` returns the norm in the real component; complex `Maximum`,
1850/// `Minimum`, and `Clamp` compare by squared norm. Signed integer `Add`,
1851/// `Multiply`, `Negate`, and `Abs` use wrapping arithmetic. `bool` supports
1852/// only copy-like identity plans and `Conj`; ambiguous arithmetic and
1853/// transcendental op/dtype pairs are rejected before any destination is
1854/// written.
1855pub fn fused_elementwise_into<T: FusedScalar>(
1856    dests: &mut [StridedViewMut<'_, T>],
1857    inputs: &[StridedView<'_, T>],
1858    plan: &FusedPlan,
1859    mut exec: Exec<'_>,
1860) -> Result<()> {
1861    validate_plan_for_scalar::<T>(plan, inputs.len(), dests.len())?;
1862    let validated = validate_shapes(dests, inputs)?;
1863    if try_static_specialization_initialized(dests, inputs, plan, validated, exec.reborrow())? {
1864        return Ok(());
1865    }
1866    interpret_fused_elementwise_into(dests, inputs, plan, exec)
1867}
1868
1869#[cfg(test)]
1870#[path = "fused/tests/tests.rs"]
1871mod tests;