1use 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
22pub(crate) struct InitializedViewsMut<'a, T> {
30 storage: *mut MaybeUninit<StridedViewMut<'a, T>>,
31 len: usize,
32}
33
34impl<'a, T> InitializedViewsMut<'a, T> {
35 pub(crate) unsafe fn new(storage: *mut MaybeUninit<StridedViewMut<'a, T>>) -> Self {
39 Self { storage, len: 0 }
40 }
41
42 pub(crate) fn push(&mut self, view: StridedViewMut<'a, T>) {
44 debug_assert!(self.len < strided_basic::OPERAND_CAP);
45 unsafe { self.storage.add(self.len).write(MaybeUninit::new(view)) };
48 self.len += 1;
49 }
50
51 pub(crate) fn as_mut_slice(&mut self) -> &mut [StridedViewMut<'a, T>] {
53 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 unsafe { (*self.storage.add(index)).assume_init_drop() };
67 }
68 }
69}
70
71#[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
86unsafe 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#[inline]
100fn operand_ptrs<T>(ptrs: &[OperandPtr<T>]) -> &[*mut T] {
101 unsafe { core::slice::from_raw_parts(ptrs.as_ptr().cast::<*mut T>(), ptrs.len()) }
103}
104
105#[inline]
107fn operand_const_ptrs<T>(ptrs: &[OperandPtr<T>]) -> &[*const T] {
108 unsafe { core::slice::from_raw_parts(ptrs.as_ptr().cast::<*const T>(), ptrs.len()) }
111}
112
113#[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#[derive(Clone, Debug, Eq, PartialEq)]
170pub struct FusedInst {
171 pub op: FusedOp,
172 pub inputs: strided_basic::OperandVec<usize>,
173}
174
175impl FusedInst {
176 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#[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 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
260pub 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 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 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 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 macro_rules! unary {
1098 ($apply:expr) => {{
1099 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 macro_rules! binary {
1131 ($apply:expr) => {{
1132 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 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 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 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 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 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
1233fn 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#[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
1273const 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 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 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 unsafe {
1310 interpret_loop_body(dst_ptrs, input_ptrs, plan, offsets, len, strides, &mut heap)
1311 };
1312 }
1313}
1314
1315#[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 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, ®s[..output], &inst.inputs);
1342 }
1343 for (output_index, &dst_ptr) in dst_ptrs.iter().enumerate() {
1344 let stride = inner_stride(strides, output_index);
1345 unsafe {
1347 *dst_ptr.offset(offsets[output_index] + i * stride) =
1348 regs[plan.outputs[output_index]];
1349 }
1350 }
1351 }
1352}
1353
1354#[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 Some(unsafe { *inputs[0].ptr() })
1364}
1365
1366#[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#[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#[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 unsafe { build_plan_fused_small(&dims, &strides_list) }
1452 } else {
1453 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 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 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 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
1516fn 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
1531fn 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
1551fn 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 let costs = unsafe { compute_costs(ordered_strides) };
1569 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 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 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 unsafe {
1621 interpret_loop_body_uninit(dst_ptrs, input_ptrs, plan, offsets, len, strides, &mut heap)
1622 };
1623 }
1624}
1625
1626#[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 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, ®s[..output], &inst.inputs);
1652 }
1653 for (output_index, &dst_ptr) in dst_ptrs.iter().enumerate() {
1654 let stride = inner_stride(strides, output_index);
1655 unsafe {
1657 *dst_ptr.offset(offsets[output_index] + i * stride) =
1658 MaybeUninit::new(regs[plan.outputs[output_index]]);
1659 }
1660 }
1661 }
1662}
1663
1664#[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
1682pub(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 unsafe { build_plan_fused_small(&dims, &strides_list) }
1749 } else {
1750 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 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 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 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
1813pub 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;