Skip to main content

tenferro_tensor/
backend.rs

1use crate::config::{
2    CompareDir, DotGeneralConfig, GatherConfig, PadConfig, ScatterConfig, SliceConfig,
3};
4use crate::types::{
5    TensorRank, TensorScalar, TensorView, TensorViewMut, TypedTensor, TypedTensorView,
6    TypedTensorViewMut,
7};
8use crate::validate::validate_convert_dtype;
9use crate::{
10    AllocationDomainId, AllocationId, DType, Error, RuntimeCacheControl, ShapeMismatch, Tensor,
11    TensorRead, TensorValue, TensorWrite, ValidationError,
12};
13use num_complex::{Complex32, Complex64};
14use std::any::TypeId;
15
16#[cfg(test)]
17mod tests;
18
19fn read_boundary_error(op: &'static str) -> crate::Error {
20    crate::Error::unsupported(
21        op,
22        "backend does not accept borrowed tensor views at this execution boundary",
23    )
24}
25
26fn validation(op: &'static str, source: ValidationError) -> crate::Error {
27    Error::validation(op, source)
28}
29
30fn invalid_argument(op: &'static str, argument: &'static str, message: impl Into<String>) -> Error {
31    Error::invalid_argument(op, argument, message)
32}
33
34fn read_tensor<'a>(op: &'static str, input: TensorRead<'a>) -> crate::Result<&'a Tensor> {
35    input.as_tensor().ok_or_else(|| read_boundary_error(op))
36}
37
38fn validate_axis_list(
39    op: &'static str,
40    role: &'static str,
41    axes: &[usize],
42    rank: usize,
43) -> crate::Result<()> {
44    let mut seen = vec![false; rank];
45    for &axis in axes {
46        if axis >= rank {
47            return Err(validation(
48                op,
49                ValidationError::AxisOutOfBounds { axis, rank },
50            ));
51        }
52        if seen[axis] {
53            return Err(validation(
54                op,
55                ValidationError::DuplicateAxis { axis, role },
56            ));
57        }
58        seen[axis] = true;
59    }
60    Ok(())
61}
62
63fn validate_role_disjoint(
64    op: &'static str,
65    first_role: &'static str,
66    first_axes: &[usize],
67    second_role: &'static str,
68    second_axes: &[usize],
69) -> crate::Result<()> {
70    for &axis in first_axes {
71        if second_axes.contains(&axis) {
72            return Err(validation(
73                op,
74                ValidationError::AxisRoleConflict {
75                    axis,
76                    first_role,
77                    second_role,
78                },
79            ));
80        }
81    }
82    Ok(())
83}
84
85/// Infer the output shape for a validated dot-general operation.
86#[doc(hidden)]
87pub fn dot_general_output_shape(
88    lhs_shape: &[usize],
89    rhs_shape: &[usize],
90    config: &DotGeneralConfig,
91    op: &'static str,
92) -> crate::Result<Vec<usize>> {
93    if config.lhs_contracting_dims.len() != config.rhs_contracting_dims.len() {
94        return Err(invalid_argument(
95            op,
96            "contracting_dims",
97            "lhs/rhs contracting dim counts differ",
98        ));
99    }
100    if config.lhs_batch_dims.len() != config.rhs_batch_dims.len() {
101        return Err(invalid_argument(
102            op,
103            "batch_dims",
104            "lhs/rhs batch dim counts differ",
105        ));
106    }
107
108    let lhs_rank = lhs_shape.len();
109    let rhs_rank = rhs_shape.len();
110    validate_axis_list(
111        op,
112        "lhs_contracting",
113        &config.lhs_contracting_dims,
114        lhs_rank,
115    )?;
116    validate_axis_list(
117        op,
118        "rhs_contracting",
119        &config.rhs_contracting_dims,
120        rhs_rank,
121    )?;
122    validate_axis_list(op, "lhs_batch", &config.lhs_batch_dims, lhs_rank)?;
123    validate_axis_list(op, "rhs_batch", &config.rhs_batch_dims, rhs_rank)?;
124    validate_role_disjoint(
125        op,
126        "lhs_contracting",
127        &config.lhs_contracting_dims,
128        "lhs_batch",
129        &config.lhs_batch_dims,
130    )?;
131    validate_role_disjoint(
132        op,
133        "rhs_contracting",
134        &config.rhs_contracting_dims,
135        "rhs_batch",
136        &config.rhs_batch_dims,
137    )?;
138
139    for (&lhs_axis, &rhs_axis) in config
140        .lhs_contracting_dims
141        .iter()
142        .zip(&config.rhs_contracting_dims)
143    {
144        if lhs_shape[lhs_axis] != rhs_shape[rhs_axis] {
145            return Err(validation(
146                op,
147                ShapeMismatch::ContractedDimensions {
148                    lhs_axis,
149                    lhs_size: lhs_shape[lhs_axis],
150                    rhs_axis,
151                    rhs_size: rhs_shape[rhs_axis],
152                }
153                .into(),
154            ));
155        }
156    }
157    for (&lhs_axis, &rhs_axis) in config.lhs_batch_dims.iter().zip(&config.rhs_batch_dims) {
158        if lhs_shape[lhs_axis] != rhs_shape[rhs_axis] {
159            return Err(validation(
160                op,
161                ShapeMismatch::ContractedDimensions {
162                    lhs_axis,
163                    lhs_size: lhs_shape[lhs_axis],
164                    rhs_axis,
165                    rhs_size: rhs_shape[rhs_axis],
166                }
167                .into(),
168            ));
169        }
170    }
171
172    let lhs_free = (0..lhs_rank)
173        .filter(|axis| {
174            !config.lhs_contracting_dims.contains(axis) && !config.lhs_batch_dims.contains(axis)
175        })
176        .map(|axis| lhs_shape[axis]);
177    let rhs_free = (0..rhs_rank)
178        .filter(|axis| {
179            !config.rhs_contracting_dims.contains(axis) && !config.rhs_batch_dims.contains(axis)
180        })
181        .map(|axis| rhs_shape[axis]);
182    let batch = config.lhs_batch_dims.iter().map(|&axis| lhs_shape[axis]);
183
184    Ok(lhs_free.chain(rhs_free).chain(batch).collect())
185}
186
187/// Validate output dtype and shape for dot-general read-into dispatch.
188#[doc(hidden)]
189pub fn validate_dot_general_read_into(
190    lhs: &TensorRead<'_>,
191    rhs: &TensorRead<'_>,
192    config: &DotGeneralConfig,
193    out: &TensorWrite<'_>,
194    op: &'static str,
195) -> crate::Result<Vec<usize>> {
196    if lhs.dtype() != rhs.dtype() {
197        return Err(validation(
198            op,
199            ValidationError::DTypeMismatch {
200                expected: crate::core_dtype(lhs.dtype()),
201                actual: crate::core_dtype(rhs.dtype()),
202            },
203        ));
204    }
205    if lhs.dtype() != out.dtype() {
206        return Err(validation(
207            op,
208            ValidationError::DTypeMismatch {
209                expected: crate::core_dtype(lhs.dtype()),
210                actual: crate::core_dtype(out.dtype()),
211            },
212        ));
213    }
214    let expected = dot_general_output_shape(lhs.shape(), rhs.shape(), config, op)?;
215    if out.shape() != expected.as_slice() {
216        return Err(validation(
217            op,
218            ShapeMismatch::ExpectedActual {
219                expected: expected.clone().into(),
220                actual: out.shape().to_vec().into(),
221            }
222            .into(),
223        ));
224    }
225    Ok(expected)
226}
227
228/// Scalar coefficient accepted by contraction accumulation backends.
229///
230/// `ContractionScalar` is intentionally narrower than [`crate::TensorScalar`]:
231/// dot-general accumulation is only defined for floating and complex tensor
232/// dtypes.
233///
234/// # Examples
235///
236/// ```rust
237/// use tenferro_tensor::{ContractionScalar, DType};
238///
239/// let alpha = ContractionScalar::F64(2.0);
240/// assert_eq!(alpha.dtype(), DType::F64);
241/// ```
242#[derive(Clone, Copy, Debug, PartialEq)]
243pub enum ContractionScalar {
244    F32(f32),
245    F64(f64),
246    C32(Complex32),
247    C64(Complex64),
248}
249
250impl ContractionScalar {
251    /// Return this scalar's tensor dtype.
252    ///
253    /// # Examples
254    ///
255    /// ```rust
256    /// use tenferro_tensor::{ContractionScalar, DType};
257    ///
258    /// assert_eq!(ContractionScalar::F32(1.0).dtype(), DType::F32);
259    /// ```
260    pub fn dtype(self) -> DType {
261        match self {
262            Self::F32(_) => DType::F32,
263            Self::F64(_) => DType::F64,
264            Self::C32(_) => DType::C32,
265            Self::C64(_) => DType::C64,
266        }
267    }
268
269    /// Return the multiplicative identity for a supported contraction dtype.
270    ///
271    /// # Examples
272    ///
273    /// ```rust
274    /// use tenferro_tensor::{ContractionScalar, DType};
275    ///
276    /// assert_eq!(ContractionScalar::one(DType::F64).unwrap(), ContractionScalar::F64(1.0));
277    /// assert!(ContractionScalar::one(DType::I32).is_err());
278    /// ```
279    /// # Errors
280    ///
281    /// Returns [`crate::Error::Validation`] with a
282    /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` is `I32`,
283    /// `I64`, or `Bool`, which do not support contraction scalar identities.
284    pub fn one(dtype: DType) -> crate::Result<Self> {
285        match dtype {
286            DType::F32 => Ok(Self::F32(1.0)),
287            DType::F64 => Ok(Self::F64(1.0)),
288            DType::C32 => Ok(Self::C32(Complex32::new(1.0, 0.0))),
289            DType::C64 => Ok(Self::C64(Complex64::new(1.0, 0.0))),
290            DType::I32 | DType::I64 | DType::Bool => Err(validation(
291                "ContractionScalar::one",
292                ValidationError::DTypeMismatch {
293                    expected: crate::core_dtype(dtype),
294                    actual: crate::core_dtype(DType::F32),
295                },
296            )),
297        }
298    }
299
300    /// Return the additive identity for a supported contraction dtype.
301    ///
302    /// # Examples
303    ///
304    /// ```rust
305    /// use tenferro_tensor::{ContractionScalar, DType};
306    ///
307    /// assert_eq!(ContractionScalar::zero(DType::F64).unwrap(), ContractionScalar::F64(0.0));
308    /// ```
309    /// # Errors
310    ///
311    /// Returns [`crate::Error::Validation`] with a
312    /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` is `I32`,
313    /// `I64`, or `Bool`, which do not support contraction scalar identities.
314    pub fn zero(dtype: DType) -> crate::Result<Self> {
315        match dtype {
316            DType::F32 => Ok(Self::F32(0.0)),
317            DType::F64 => Ok(Self::F64(0.0)),
318            DType::C32 => Ok(Self::C32(Complex32::new(0.0, 0.0))),
319            DType::C64 => Ok(Self::C64(Complex64::new(0.0, 0.0))),
320            DType::I32 | DType::I64 | DType::Bool => Err(validation(
321                "ContractionScalar::zero",
322                ValidationError::DTypeMismatch {
323                    expected: crate::core_dtype(dtype),
324                    actual: crate::core_dtype(DType::F32),
325                },
326            )),
327        }
328    }
329}
330
331/// Output-update semantics for dot-general accumulation.
332///
333/// This keeps contraction axes in [`DotGeneralConfig`] and output update
334/// semantics here, so cached and non-cached backend traits can share the same
335/// accumulation contract.
336///
337/// # Examples
338///
339/// ```rust
340/// use tenferro_tensor::{ContractionScalar, DotGeneralAccumulation, DType};
341///
342/// let accum = DotGeneralAccumulation::overwrite(DType::F64).unwrap();
343/// assert_eq!(accum.alpha, ContractionScalar::F64(1.0));
344/// assert_eq!(accum.beta, ContractionScalar::F64(0.0));
345/// ```
346#[derive(Clone, Copy, Debug, PartialEq)]
347pub struct DotGeneralAccumulation {
348    pub lhs_conj: bool,
349    pub rhs_conj: bool,
350    pub alpha: ContractionScalar,
351    pub beta: ContractionScalar,
352}
353
354/// One matrix multiply in a grouped GEMM over shared flat buffers.
355///
356/// Offsets are element offsets into the corresponding shared lhs, rhs, and
357/// output buffers. Each job computes a column-major `rows x cols` output block
358/// from a column-major `rows x contracted` lhs block and a column-major
359/// `contracted x cols` rhs block.
360///
361/// Provider implementations receive these descriptors through the public
362/// grouped-GEMM request accessor. The engine validates ranges and pairwise
363/// output disjointness before provider entry.
364#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
365pub struct GroupedGemmJob {
366    out_offset: usize,
367    lhs_offset: usize,
368    rhs_offset: usize,
369    rows: usize,
370    contracted: usize,
371    cols: usize,
372}
373
374impl GroupedGemmJob {
375    /// Construct a column-major grouped-GEMM job over shared flat buffers.
376    #[allow(clippy::too_many_arguments)]
377    pub fn new(
378        out_offset: usize,
379        lhs_offset: usize,
380        rhs_offset: usize,
381        rows: usize,
382        contracted: usize,
383        cols: usize,
384    ) -> Self {
385        Self {
386            out_offset,
387            lhs_offset,
388            rhs_offset,
389            rows,
390            contracted,
391            cols,
392        }
393    }
394
395    /// Return the output element offset.
396    pub fn out_offset(&self) -> usize {
397        self.out_offset
398    }
399
400    /// Return the left-input element offset.
401    pub fn lhs_offset(&self) -> usize {
402        self.lhs_offset
403    }
404
405    /// Return the right-input element offset.
406    pub fn rhs_offset(&self) -> usize {
407        self.rhs_offset
408    }
409
410    /// Return the output row count.
411    pub fn rows(&self) -> usize {
412        self.rows
413    }
414
415    /// Return the contracted dimension.
416    pub fn contracted(&self) -> usize {
417        self.contracted
418    }
419
420    /// Return the output column count.
421    pub fn cols(&self) -> usize {
422        self.cols
423    }
424}
425
426/// Shared scalar/update metadata for grouped GEMM execution.
427#[doc(hidden)]
428#[derive(Clone, Copy, Debug, PartialEq)]
429pub struct GroupedGemmConfig<'a> {
430    jobs: &'a [GroupedGemmJob],
431    accumulation: DotGeneralAccumulation,
432}
433
434impl<'a> GroupedGemmConfig<'a> {
435    pub fn new(jobs: &'a [GroupedGemmJob], accumulation: DotGeneralAccumulation) -> Self {
436        Self { jobs, accumulation }
437    }
438
439    pub fn jobs(&self) -> &'a [GroupedGemmJob] {
440        self.jobs
441    }
442
443    pub fn accumulation(&self) -> DotGeneralAccumulation {
444        self.accumulation
445    }
446}
447
448impl DotGeneralAccumulation {
449    fn identity(
450        op: &'static str,
451        dtype: DType,
452        multiplicative: bool,
453    ) -> crate::Result<ContractionScalar> {
454        let result = if multiplicative {
455            ContractionScalar::one(dtype)
456        } else {
457            ContractionScalar::zero(dtype)
458        };
459        result.map_err(|error| match error {
460            Error::Validation { source, .. } => validation(op, source),
461            error => error,
462        })
463    }
464
465    /// Return overwrite semantics, `out = lhs dot rhs`, for `dtype`.
466    ///
467    /// # Examples
468    ///
469    /// ```rust
470    /// use tenferro_tensor::{ContractionScalar, DotGeneralAccumulation, DType};
471    ///
472    /// let accum = DotGeneralAccumulation::overwrite(DType::F64).unwrap();
473    /// assert_eq!(accum.alpha, ContractionScalar::F64(1.0));
474    /// assert_eq!(accum.beta, ContractionScalar::F64(0.0));
475    /// ```
476    ///
477    /// # Errors
478    ///
479    /// Returns [`crate::Error::Validation`] with a
480    /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` does not
481    /// support contraction scalar identities.
482    pub fn overwrite(dtype: DType) -> crate::Result<Self> {
483        Ok(Self {
484            lhs_conj: false,
485            rhs_conj: false,
486            alpha: Self::identity("DotGeneralAccumulation::overwrite", dtype, true)?,
487            beta: Self::identity("DotGeneralAccumulation::overwrite", dtype, false)?,
488        })
489    }
490
491    /// Return additive update semantics, `out += lhs dot rhs`, for `dtype`.
492    ///
493    /// # Examples
494    ///
495    /// ```rust
496    /// use tenferro_tensor::{ContractionScalar, DType, DotGeneralAccumulation};
497    ///
498    /// let accum = DotGeneralAccumulation::add_to(DType::F64)?;
499    /// assert_eq!(accum.alpha, ContractionScalar::F64(1.0));
500    /// assert_eq!(accum.beta, ContractionScalar::F64(1.0));
501    /// # Ok::<(), tenferro_tensor::Error>(())
502    /// ```
503    /// # Errors
504    ///
505    /// Returns [`crate::Error::Validation`] with a
506    /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` does not
507    /// support contraction scalar identities.
508    pub fn add_to(dtype: DType) -> crate::Result<Self> {
509        Ok(Self {
510            lhs_conj: false,
511            rhs_conj: false,
512            alpha: Self::identity("DotGeneralAccumulation::add_to", dtype, true)?,
513            beta: Self::identity("DotGeneralAccumulation::add_to", dtype, true)?,
514        })
515    }
516
517    /// Return scaled update semantics, `out = alpha * lhs dot rhs + beta * out`.
518    ///
519    /// # Examples
520    ///
521    /// ```rust
522    /// use tenferro_tensor::{ContractionScalar, DotGeneralAccumulation};
523    ///
524    /// let accum = DotGeneralAccumulation::scaled(
525    ///     ContractionScalar::F32(0.5),
526    ///     ContractionScalar::F32(2.0),
527    /// )?;
528    /// assert_eq!(accum.alpha, ContractionScalar::F32(0.5));
529    /// # Ok::<(), tenferro_tensor::Error>(())
530    /// ```
531    /// # Errors
532    ///
533    /// Returns [`crate::Error::Validation`] with a
534    /// [`crate::ValidationError::DTypeMismatch`] source when `alpha` and `beta`
535    /// have different dtypes.
536    pub fn scaled(alpha: ContractionScalar, beta: ContractionScalar) -> crate::Result<Self> {
537        if alpha.dtype() != beta.dtype() {
538            return Err(validation(
539                "DotGeneralAccumulation::scaled",
540                ValidationError::DTypeMismatch {
541                    expected: crate::core_dtype(alpha.dtype()),
542                    actual: crate::core_dtype(beta.dtype()),
543                },
544            ));
545        }
546        Ok(Self {
547            lhs_conj: false,
548            rhs_conj: false,
549            alpha,
550            beta,
551        })
552    }
553
554    fn validate_for_dtype(self, dtype: DType) -> crate::Result<()> {
555        for scalar in [self.alpha, self.beta] {
556            if scalar.dtype() != dtype {
557                return Err(validation(
558                    "dot_general",
559                    ValidationError::DTypeMismatch {
560                        expected: crate::core_dtype(scalar.dtype()),
561                        actual: crate::core_dtype(dtype),
562                    },
563                ));
564            }
565        }
566        Ok(())
567    }
568}
569
570#[doc(hidden)]
571pub fn validate_dot_general_accumulation(
572    lhs: &TensorRead<'_>,
573    rhs: &TensorRead<'_>,
574    config: &DotGeneralConfig,
575    accumulation: DotGeneralAccumulation,
576    out: &TensorWrite<'_>,
577    op: &'static str,
578) -> crate::Result<Vec<usize>> {
579    let shape = validate_dot_general_read_into(lhs, rhs, config, out, op)?;
580    accumulation.validate_for_dtype(lhs.dtype())?;
581    Ok(shape)
582}
583
584#[doc(hidden)]
585pub fn dot_general_accum_via_temp<B: TensorDot + ?Sized>(
586    backend: &mut B,
587    lhs: TensorRead<'_>,
588    rhs: TensorRead<'_>,
589    config: &DotGeneralConfig,
590    accumulation: DotGeneralAccumulation,
591    mut out: TensorWrite<'_>,
592) -> crate::Result<()> {
593    validate_dot_general_accumulation(&lhs, &rhs, config, accumulation, &out, "dot_general")?;
594    let dot = backend.dot_general_with_conj_read(
595        lhs,
596        rhs,
597        config,
598        accumulation.lhs_conj,
599        accumulation.rhs_conj,
600    )?;
601    accumulate_dot_result_into(&dot, accumulation, &mut out)
602}
603
604fn grouped_checked_product(
605    op: &'static str,
606    role: &'static str,
607    dims: &[usize],
608) -> crate::Result<usize> {
609    dims.iter().try_fold(1usize, |acc, &dim| {
610        acc.checked_mul(dim).ok_or_else(|| {
611            invalid_argument(
612                op,
613                role,
614                format!("logical element count overflows usize for shape {dims:?}"),
615            )
616        })
617    })
618}
619
620fn checked_gemm_span(
621    op: &'static str,
622    role: &'static str,
623    offset: usize,
624    rows: usize,
625    cols: usize,
626) -> crate::Result<Option<std::ops::Range<usize>>> {
627    let len = rows.checked_mul(cols).ok_or_else(|| {
628        invalid_argument(
629            op,
630            role,
631            format!("matrix element count overflows usize: rows={rows} cols={cols}"),
632        )
633    })?;
634    if len == 0 {
635        return Ok(None);
636    }
637    let end = offset.checked_add(len).ok_or_else(|| {
638        invalid_argument(
639            op,
640            role,
641            format!("matrix range overflows usize: offset={offset} len={len}"),
642        )
643    })?;
644    Ok(Some(offset..end))
645}
646
647fn validate_grouped_gemm_range(
648    op: &'static str,
649    role: &'static str,
650    len: usize,
651    range: Option<std::ops::Range<usize>>,
652) -> crate::Result<()> {
653    let Some(range) = range else {
654        return Ok(());
655    };
656    if range.end > len {
657        return Err(invalid_argument(
658            op,
659            role,
660            format!(
661                "matrix range {}..{} exceeds shared buffer logical length {len}",
662                range.start, range.end
663            ),
664        ));
665    }
666    Ok(())
667}
668
669#[doc(hidden)]
670pub fn validate_grouped_gemm(
671    lhs: &TensorRead<'_>,
672    rhs: &TensorRead<'_>,
673    out: &TensorWrite<'_>,
674    config: &GroupedGemmConfig<'_>,
675    op: &'static str,
676) -> crate::Result<()> {
677    if lhs.dtype() != rhs.dtype() {
678        return Err(validation(
679            op,
680            ValidationError::DTypeMismatch {
681                expected: crate::core_dtype(lhs.dtype()),
682                actual: crate::core_dtype(rhs.dtype()),
683            },
684        ));
685    }
686    if lhs.dtype() != out.dtype() {
687        return Err(validation(
688            op,
689            ValidationError::DTypeMismatch {
690                expected: crate::core_dtype(lhs.dtype()),
691                actual: crate::core_dtype(out.dtype()),
692            },
693        ));
694    }
695    config.accumulation.validate_for_dtype(lhs.dtype())?;
696
697    let lhs_len = grouped_checked_product(op, "lhs", lhs.shape())?;
698    let rhs_len = grouped_checked_product(op, "rhs", rhs.shape())?;
699    let out_len = grouped_checked_product(op, "out", out.shape())?;
700    // Grouped GEMM job count is runtime-controlled and can be large. Keep the
701    // validation ranges in a reserved Vec, not SmallVec, so arbitrary batches
702    // avoid inline-capacity tuning and can be sorted for O(n log n) overlap
703    // validation.
704    let mut out_ranges = Vec::<(usize, std::ops::Range<usize>)>::with_capacity(config.jobs.len());
705    for (idx, job) in config.jobs.iter().enumerate() {
706        validate_grouped_gemm_range(
707            op,
708            "lhs",
709            lhs_len,
710            checked_gemm_span(op, "lhs", job.lhs_offset, job.rows, job.contracted)?,
711        )?;
712        validate_grouped_gemm_range(
713            op,
714            "rhs",
715            rhs_len,
716            checked_gemm_span(op, "rhs", job.rhs_offset, job.contracted, job.cols)?,
717        )?;
718        let out_range = checked_gemm_span(op, "out", job.out_offset, job.rows, job.cols)?;
719        validate_grouped_gemm_range(op, "out", out_len, out_range.clone())?;
720        if let Some(out_range) = out_range {
721            out_ranges.push((idx, out_range));
722        }
723    }
724    out_ranges.sort_unstable_by_key(|(_, range)| range.start);
725    for pair in out_ranges.windows(2) {
726        let (prev_idx, previous) = &pair[0];
727        let (idx, current) = &pair[1];
728        if previous.end > current.start {
729            return Err(invalid_argument(
730                op,
731                "jobs",
732                format!(
733                    "grouped GEMM output range for job {idx} overlaps job {prev_idx} range {}..{}",
734                    previous.start, previous.end
735                ),
736            ));
737        }
738    }
739    Ok(())
740}
741
742fn add_element_offsets(
743    op: &'static str,
744    base: isize,
745    offset: usize,
746    role: &'static str,
747) -> crate::Result<isize> {
748    let offset = isize::try_from(offset).map_err(|_| {
749        invalid_argument(op, role, format!("offset {offset} does not fit in isize"))
750    })?;
751    base.checked_add(offset).ok_or_else(|| {
752        invalid_argument(
753            op,
754            role,
755            format!("offset overflows isize: base={base} offset={offset}"),
756        )
757    })
758}
759
760fn dim_stride(op: &'static str, dim: usize, role: &'static str) -> crate::Result<isize> {
761    isize::try_from(dim).map_err(|_| {
762        invalid_argument(
763            op,
764            role,
765            format!("leading dimension {dim} does not fit in isize"),
766        )
767    })
768}
769
770fn typed_read_storage<'a, T: crate::TensorScalar>(
771    tensor: &'a TypedTensor<T>,
772    op: &'static str,
773) -> crate::Result<(&'a [T], isize)> {
774    tensor.host_data().map(|data| (data, 0)).map_err(|_| {
775        crate::Error::runtime_state(
776            op,
777            "grouped GEMM default path requires host-backed tensor storage",
778        )
779    })
780}
781
782fn grouped_gemm_default_config() -> DotGeneralConfig {
783    // DotGeneralConfig owns Vec fields, so this rank-2 fallback config follows
784    // that API boundary rather than introducing SmallVec locally.
785    DotGeneralConfig {
786        lhs_contracting_dims: vec![1],
787        rhs_contracting_dims: vec![0],
788        lhs_batch_dims: Vec::new(),
789        rhs_batch_dims: Vec::new(),
790    }
791}
792
793trait GroupedGemmDType<T> {
794    fn wrap_read(view: TypedTensorView<'_, T>) -> TensorView<'_>;
795    fn wrap_write(view: TypedTensorViewMut<'_, T>) -> TensorViewMut<'_>;
796}
797
798struct GroupedF32;
799struct GroupedF64;
800struct GroupedC32;
801struct GroupedC64;
802
803impl GroupedGemmDType<f32> for GroupedF32 {
804    fn wrap_read(view: TypedTensorView<'_, f32>) -> TensorView<'_> {
805        TensorView::F32(view)
806    }
807
808    fn wrap_write(view: TypedTensorViewMut<'_, f32>) -> TensorViewMut<'_> {
809        TensorViewMut::F32(view)
810    }
811}
812
813impl GroupedGemmDType<f64> for GroupedF64 {
814    fn wrap_read(view: TypedTensorView<'_, f64>) -> TensorView<'_> {
815        TensorView::F64(view)
816    }
817
818    fn wrap_write(view: TypedTensorViewMut<'_, f64>) -> TensorViewMut<'_> {
819        TensorViewMut::F64(view)
820    }
821}
822
823impl GroupedGemmDType<Complex32> for GroupedC32 {
824    fn wrap_read(view: TypedTensorView<'_, Complex32>) -> TensorView<'_> {
825        TensorView::C32(view)
826    }
827
828    fn wrap_write(view: TypedTensorViewMut<'_, Complex32>) -> TensorViewMut<'_> {
829        TensorViewMut::C32(view)
830    }
831}
832
833impl GroupedGemmDType<Complex64> for GroupedC64 {
834    fn wrap_read(view: TypedTensorView<'_, Complex64>) -> TensorView<'_> {
835        TensorView::C64(view)
836    }
837
838    fn wrap_write(view: TypedTensorViewMut<'_, Complex64>) -> TensorViewMut<'_> {
839        TensorViewMut::C64(view)
840    }
841}
842
843#[allow(clippy::too_many_arguments)]
844fn grouped_gemm_default_loop<B, T, V>(
845    backend: &mut B,
846    lhs_data: &[T],
847    lhs_base: isize,
848    rhs_data: &[T],
849    rhs_base: isize,
850    out_view: &mut TypedTensorViewMut<'_, T>,
851    config: &GroupedGemmConfig<'_>,
852) -> crate::Result<()>
853where
854    B: TensorDot + ?Sized,
855    T: 'static,
856    V: GroupedGemmDType<T>,
857{
858    let op = "grouped_gemm";
859    let dot_config = grouped_gemm_default_config();
860    for job in config.jobs {
861        let lhs_offset = add_element_offsets(op, lhs_base, job.lhs_offset, "lhs")?;
862        let rhs_offset = add_element_offsets(op, rhs_base, job.rhs_offset, "rhs")?;
863        let out_offset = add_element_offsets(op, out_view.offset(), job.out_offset, "out")?;
864        let lhs_rows = dim_stride(op, job.rows, "lhs")?;
865        let rhs_rows = dim_stride(op, job.contracted, "rhs")?;
866        let out_rows = dim_stride(op, job.rows, "out")?;
867        // TypedTensorView constructors own Vec shape/stride metadata. These
868        // fallback rank-2 views are short-lived, but SmallVec is not usable
869        // without changing the view API.
870        let lhs_matrix = TypedTensorView::from_slice(
871            vec![job.rows, job.contracted],
872            vec![1, lhs_rows],
873            lhs_offset,
874            lhs_data,
875        )?;
876        let rhs_matrix = TypedTensorView::from_slice(
877            vec![job.contracted, job.cols],
878            vec![1, rhs_rows],
879            rhs_offset,
880            rhs_data,
881        )?;
882        let out_storage = out_view.host_storage_mut()?;
883        let out_matrix = TypedTensorViewMut::from_slice(
884            vec![job.rows, job.cols],
885            vec![1, out_rows],
886            out_offset,
887            out_storage,
888        )?;
889        backend.dot_general_read_into_accum(
890            TensorRead::from_view(V::wrap_read(lhs_matrix)),
891            TensorRead::from_view(V::wrap_read(rhs_matrix)),
892            &dot_config,
893            config.accumulation,
894            TensorWrite::from_view(V::wrap_write(out_matrix)),
895        )?;
896    }
897    Ok(())
898}
899
900#[doc(hidden)]
901pub fn grouped_gemm_via_sequential<B>(
902    backend: &mut B,
903    lhs: TensorRead<'_>,
904    rhs: TensorRead<'_>,
905    config: &GroupedGemmConfig<'_>,
906    mut out: TensorWrite<'_>,
907) -> crate::Result<()>
908where
909    B: TensorDot + ?Sized,
910{
911    validate_grouped_gemm(&lhs, &rhs, &out, config, "grouped_gemm")?;
912    macro_rules! dispatch {
913        ($variant:ident, $wrapper:ty) => {
914            match (&lhs, &rhs, &mut out) {
915                (
916                    TensorRead::Tensor(Tensor::$variant(a)),
917                    TensorRead::Tensor(Tensor::$variant(b)),
918                    TensorWrite::Tensor(Tensor::$variant(c)),
919                ) => {
920                    let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
921                    let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
922                    let mut c_view = c.as_view_mut();
923                    return grouped_gemm_default_loop::<_, _, $wrapper>(
924                        backend,
925                        a_data,
926                        a_base,
927                        b_data,
928                        b_base,
929                        &mut c_view,
930                        config,
931                    );
932                }
933                (
934                    TensorRead::Tensor(Tensor::$variant(a)),
935                    TensorRead::View(TensorView::$variant(b)),
936                    TensorWrite::Tensor(Tensor::$variant(c)),
937                ) => {
938                    let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
939                    let mut c_view = c.as_view_mut();
940                    return grouped_gemm_default_loop::<_, _, $wrapper>(
941                        backend,
942                        a_data,
943                        a_base,
944                        b.host_storage()?,
945                        b.offset(),
946                        &mut c_view,
947                        config,
948                    );
949                }
950                (
951                    TensorRead::View(TensorView::$variant(a)),
952                    TensorRead::Tensor(Tensor::$variant(b)),
953                    TensorWrite::Tensor(Tensor::$variant(c)),
954                ) => {
955                    let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
956                    let mut c_view = c.as_view_mut();
957                    return grouped_gemm_default_loop::<_, _, $wrapper>(
958                        backend,
959                        a.host_storage()?,
960                        a.offset(),
961                        b_data,
962                        b_base,
963                        &mut c_view,
964                        config,
965                    );
966                }
967                (
968                    TensorRead::View(TensorView::$variant(a)),
969                    TensorRead::View(TensorView::$variant(b)),
970                    TensorWrite::Tensor(Tensor::$variant(c)),
971                ) => {
972                    let mut c_view = c.as_view_mut();
973                    return grouped_gemm_default_loop::<_, _, $wrapper>(
974                        backend,
975                        a.host_storage()?,
976                        a.offset(),
977                        b.host_storage()?,
978                        b.offset(),
979                        &mut c_view,
980                        config,
981                    );
982                }
983                (
984                    TensorRead::Tensor(Tensor::$variant(a)),
985                    TensorRead::Tensor(Tensor::$variant(b)),
986                    TensorWrite::View(TensorViewMut::$variant(c)),
987                ) => {
988                    let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
989                    let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
990                    return grouped_gemm_default_loop::<_, _, $wrapper>(
991                        backend, a_data, a_base, b_data, b_base, c, config,
992                    );
993                }
994                (
995                    TensorRead::Tensor(Tensor::$variant(a)),
996                    TensorRead::View(TensorView::$variant(b)),
997                    TensorWrite::View(TensorViewMut::$variant(c)),
998                ) => {
999                    let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
1000                    return grouped_gemm_default_loop::<_, _, $wrapper>(
1001                        backend,
1002                        a_data,
1003                        a_base,
1004                        b.host_storage()?,
1005                        b.offset(),
1006                        c,
1007                        config,
1008                    );
1009                }
1010                (
1011                    TensorRead::View(TensorView::$variant(a)),
1012                    TensorRead::Tensor(Tensor::$variant(b)),
1013                    TensorWrite::View(TensorViewMut::$variant(c)),
1014                ) => {
1015                    let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
1016                    return grouped_gemm_default_loop::<_, _, $wrapper>(
1017                        backend,
1018                        a.host_storage()?,
1019                        a.offset(),
1020                        b_data,
1021                        b_base,
1022                        c,
1023                        config,
1024                    );
1025                }
1026                (
1027                    TensorRead::View(TensorView::$variant(a)),
1028                    TensorRead::View(TensorView::$variant(b)),
1029                    TensorWrite::View(TensorViewMut::$variant(c)),
1030                ) => {
1031                    return grouped_gemm_default_loop::<_, _, $wrapper>(
1032                        backend,
1033                        a.host_storage()?,
1034                        a.offset(),
1035                        b.host_storage()?,
1036                        b.offset(),
1037                        c,
1038                        config,
1039                    );
1040                }
1041                _ => {}
1042            }
1043        };
1044    }
1045
1046    dispatch!(F32, GroupedF32);
1047    dispatch!(F64, GroupedF64);
1048    dispatch!(C32, GroupedC32);
1049    dispatch!(C64, GroupedC64);
1050    Err(validation(
1051        "grouped_gemm",
1052        ValidationError::DTypeMismatch {
1053            expected: crate::core_dtype(lhs.dtype()),
1054            actual: crate::core_dtype(out.dtype()),
1055        },
1056    ))
1057}
1058
1059fn grouped_gemm_default<B>(
1060    backend: &mut B,
1061    lhs: TensorRead<'_>,
1062    rhs: TensorRead<'_>,
1063    config: &GroupedGemmConfig<'_>,
1064    out: TensorWrite<'_>,
1065) -> crate::Result<()>
1066where
1067    B: TensorDot + ?Sized,
1068{
1069    grouped_gemm_via_sequential(backend, lhs, rhs, config, out)
1070}
1071
1072#[doc(hidden)]
1073pub fn accumulate_dot_result_into(
1074    dot: &Tensor,
1075    accumulation: DotGeneralAccumulation,
1076    out: &mut TensorWrite<'_>,
1077) -> crate::Result<()> {
1078    macro_rules! dispatch {
1079        ($variant:ident, $ty:ty) => {
1080            if let (
1081                Tensor::$variant(dot),
1082                ContractionScalar::$variant(alpha),
1083                ContractionScalar::$variant(beta),
1084            ) = (dot, accumulation.alpha, accumulation.beta)
1085            {
1086                match out {
1087                    TensorWrite::Tensor(Tensor::$variant(out)) => {
1088                        let mut out = out.as_view_mut();
1089                        accumulate_typed(dot.as_slice()?, alpha, beta, &mut out)?;
1090                        return Ok(());
1091                    }
1092                    TensorWrite::View(crate::TensorViewMut::$variant(out)) => {
1093                        accumulate_typed(dot.as_slice()?, alpha, beta, out)?;
1094                        return Ok(());
1095                    }
1096                    _ => {}
1097                }
1098            }
1099        };
1100    }
1101
1102    dispatch!(F32, f32);
1103    dispatch!(F64, f64);
1104    dispatch!(C32, Complex32);
1105    dispatch!(C64, Complex64);
1106
1107    Err(validation(
1108        "dot_general",
1109        ValidationError::DTypeMismatch {
1110            expected: crate::core_dtype(accumulation.alpha.dtype()),
1111            actual: crate::core_dtype(dot.dtype()),
1112        },
1113    ))
1114}
1115
1116fn accumulate_typed<T>(
1117    dot: &[T],
1118    alpha: T,
1119    beta: T,
1120    out: &mut TypedTensorViewMut<'_, T>,
1121) -> crate::Result<()>
1122where
1123    T: Copy
1124        + PartialEq
1125        + std::ops::Add<Output = T>
1126        + std::ops::Mul<Output = T>
1127        + num_traits::Zero
1128        + 'static,
1129{
1130    let beta_is_zero = beta == T::zero();
1131    if let Some(output) = compact_host_accumulation_slice(out, dot.len())? {
1132        for (output, dot_value) in output.iter_mut().zip(dot.iter().copied()) {
1133            // INVARIANT: beta == 0 follows BLAS GEMM semantics and does not read
1134            // the existing output element; beta != 0 requires an initialized
1135            // TensorWrite target and performs a read-modify-write update.
1136            *output = if beta_is_zero {
1137                alpha * dot_value
1138            } else {
1139                alpha * dot_value + beta * *output
1140            };
1141        }
1142        return Ok(());
1143    }
1144
1145    for (linear, dot_value) in dot.iter().copied().enumerate() {
1146        let indices = flat_to_multi_for_shape(out.shape(), linear);
1147        let output = out.get_mut(&indices).ok_or_else(|| {
1148            invalid_argument(
1149                "dot_general",
1150                "output",
1151                format!("index {indices:?} is outside accumulation target"),
1152            )
1153        })?;
1154        // INVARIANT: beta == 0 follows BLAS GEMM semantics and does not read
1155        // the existing output element; beta != 0 requires an initialized
1156        // TensorWrite target and performs a read-modify-write update.
1157        *output = if beta_is_zero {
1158            alpha * dot_value
1159        } else {
1160            alpha * dot_value + beta * *output
1161        };
1162    }
1163    Ok(())
1164}
1165
1166fn compact_host_accumulation_slice<'a, T: 'static>(
1167    out: &'a mut TypedTensorViewMut<'_, T>,
1168    expected_len: usize,
1169) -> crate::Result<Option<&'a mut [T]>> {
1170    if out.backend_buffer().is_some()
1171        || out.n_elements() != expected_len
1172        || !out.is_col_major_contiguous()?
1173    {
1174        return Ok(None);
1175    }
1176
1177    let start = usize::try_from(out.offset()).map_err(|_| {
1178        invalid_argument("dot_general", "output", "compact output offset is negative")
1179    })?;
1180    let end = start
1181        .checked_add(expected_len)
1182        .ok_or_else(|| validation("dot_general", ValidationError::IntegerOverflow))?;
1183    out.host_storage_mut()?
1184        .get_mut(start..end)
1185        .map(Some)
1186        .ok_or_else(|| {
1187            invalid_argument(
1188                "dot_general",
1189                "output",
1190                "compact output is outside its backing storage",
1191            )
1192        })
1193}
1194
1195fn flat_to_multi_for_shape(shape: &[usize], mut linear: usize) -> Vec<usize> {
1196    let mut indices = Vec::with_capacity(shape.len());
1197    for &dim in shape {
1198        if dim == 0 {
1199            indices.push(0);
1200        } else {
1201            indices.push(linear % dim);
1202            linear /= dim;
1203        }
1204    }
1205    indices
1206}
1207
1208/// Canonical elementwise fusion plan shared between segmented execution and backends.
1209#[doc(hidden)]
1210#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1211pub struct ElementwiseFusionPlan {
1212    dtype: crate::DType,
1213    input_count: usize,
1214    // Keep view metadata in Vecs. A/B benchmarking on the broadcast_mul
1215    // path showed SmallVec made this metadata path about 6-7% slower.
1216    input_views: Vec<ElementwiseFusionInputView>,
1217    outputs: Vec<usize>,
1218    ops: Vec<ElementwiseFusionInst>,
1219}
1220
1221/// Metadata-only view applied to one backend fusion input.
1222#[doc(hidden)]
1223#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1224pub enum ElementwiseFusionInputView {
1225    Identity,
1226    BroadcastInDim {
1227        // Vec is intentional here; see ElementwiseFusionPlan::input_views.
1228        shape: Vec<usize>,
1229        dims: Vec<usize>,
1230    },
1231}
1232
1233/// One node in a canonical elementwise fusion plan.
1234#[doc(hidden)]
1235#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1236pub struct ElementwiseFusionInst {
1237    op: ElementwiseFusionOp,
1238    inputs: Vec<usize>,
1239}
1240
1241tenferro_core_ops::define_elementwise_fusion_op!();
1242
1243impl ElementwiseFusionPlan {
1244    /// Build a backend elementwise fusion plan.
1245    ///
1246    /// # Examples
1247    ///
1248    /// ```rust
1249    /// use tenferro_tensor::backend::{
1250    ///     ElementwiseFusionInst, ElementwiseFusionOp, ElementwiseFusionPlan,
1251    /// };
1252    /// use tenferro_tensor::DType;
1253    ///
1254    /// let plan = ElementwiseFusionPlan::new(
1255    ///     DType::F64,
1256    ///     2,
1257    ///     vec![2],
1258    ///     vec![ElementwiseFusionInst::new(ElementwiseFusionOp::Add, vec![0, 1])],
1259    /// );
1260    /// assert_eq!(plan.input_count(), 2);
1261    /// ```
1262    pub fn new(
1263        dtype: crate::DType,
1264        input_count: usize,
1265        outputs: Vec<usize>,
1266        ops: Vec<ElementwiseFusionInst>,
1267    ) -> Self {
1268        Self::with_input_views(
1269            dtype,
1270            vec![ElementwiseFusionInputView::Identity; input_count],
1271            outputs,
1272            ops,
1273        )
1274    }
1275
1276    /// Build a backend elementwise fusion plan with input view metadata.
1277    ///
1278    /// # Examples
1279    ///
1280    /// ```rust
1281    /// use tenferro_tensor::backend::{
1282    ///     ElementwiseFusionInputView, ElementwiseFusionInst, ElementwiseFusionOp,
1283    ///     ElementwiseFusionPlan,
1284    /// };
1285    /// use tenferro_tensor::DType;
1286    ///
1287    /// let plan = ElementwiseFusionPlan::with_input_views(
1288    ///     DType::F64,
1289    ///     vec![ElementwiseFusionInputView::broadcast_in_dim(vec![2, 3], vec![0])],
1290    ///     vec![1],
1291    ///     vec![ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0])],
1292    /// );
1293    /// assert_eq!(plan.input_count(), 1);
1294    /// ```
1295    pub fn with_input_views(
1296        dtype: crate::DType,
1297        input_views: impl IntoIterator<Item = ElementwiseFusionInputView>,
1298        outputs: Vec<usize>,
1299        ops: Vec<ElementwiseFusionInst>,
1300    ) -> Self {
1301        let input_views = input_views.into_iter().collect::<Vec<_>>();
1302        let input_count = input_views.len();
1303        Self {
1304            dtype,
1305            input_count,
1306            input_views,
1307            outputs,
1308            ops,
1309        }
1310    }
1311
1312    /// Return the scalar dtype expected by this fusion plan.
1313    ///
1314    /// # Examples
1315    ///
1316    /// ```rust
1317    /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1318    /// use tenferro_tensor::DType;
1319    ///
1320    /// let plan = ElementwiseFusionPlan::new(DType::F32, 0, Vec::new(), Vec::new());
1321    /// assert_eq!(plan.dtype(), DType::F32);
1322    /// ```
1323    pub fn dtype(&self) -> crate::DType {
1324        self.dtype
1325    }
1326
1327    /// Return the number of input tensors expected by this plan.
1328    ///
1329    /// # Examples
1330    ///
1331    /// ```rust
1332    /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1333    /// use tenferro_tensor::DType;
1334    ///
1335    /// let plan = ElementwiseFusionPlan::new(DType::F64, 3, Vec::new(), Vec::new());
1336    /// assert_eq!(plan.input_count(), 3);
1337    /// ```
1338    pub fn input_count(&self) -> usize {
1339        self.input_count
1340    }
1341
1342    /// Return metadata views applied to fusion inputs before executing ops.
1343    ///
1344    /// # Examples
1345    ///
1346    /// ```rust
1347    /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1348    /// use tenferro_tensor::DType;
1349    ///
1350    /// let plan = ElementwiseFusionPlan::new(DType::F64, 2, Vec::new(), Vec::new());
1351    /// assert_eq!(plan.input_views().len(), 2);
1352    /// ```
1353    pub fn input_views(&self) -> &[ElementwiseFusionInputView] {
1354        &self.input_views
1355    }
1356
1357    /// Return the value ids selected as fusion outputs.
1358    ///
1359    /// # Examples
1360    ///
1361    /// ```rust
1362    /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1363    /// use tenferro_tensor::DType;
1364    ///
1365    /// let plan = ElementwiseFusionPlan::new(DType::F64, 0, vec![0], Vec::new());
1366    /// assert_eq!(plan.outputs(), &[0]);
1367    /// ```
1368    pub fn outputs(&self) -> &[usize] {
1369        &self.outputs
1370    }
1371
1372    /// Return the fused elementwise instruction sequence.
1373    ///
1374    /// # Examples
1375    ///
1376    /// ```rust
1377    /// use tenferro_tensor::backend::{
1378    ///     ElementwiseFusionInst, ElementwiseFusionOp, ElementwiseFusionPlan,
1379    /// };
1380    /// use tenferro_tensor::DType;
1381    ///
1382    /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0]);
1383    /// let plan = ElementwiseFusionPlan::new(DType::F64, 1, vec![1], vec![inst]);
1384    /// assert_eq!(plan.ops().len(), 1);
1385    /// ```
1386    pub fn ops(&self) -> &[ElementwiseFusionInst] {
1387        &self.ops
1388    }
1389}
1390
1391impl ElementwiseFusionInputView {
1392    /// Build metadata for a `BroadcastInDim` fusion input view.
1393    ///
1394    /// # Examples
1395    ///
1396    /// ```rust
1397    /// use tenferro_tensor::backend::ElementwiseFusionInputView;
1398    ///
1399    /// let view = ElementwiseFusionInputView::broadcast_in_dim(vec![2, 3], vec![0]);
1400    /// assert!(matches!(view, ElementwiseFusionInputView::BroadcastInDim { .. }));
1401    /// ```
1402    pub fn broadcast_in_dim(
1403        shape: impl IntoIterator<Item = usize>,
1404        dims: impl IntoIterator<Item = usize>,
1405    ) -> Self {
1406        Self::BroadcastInDim {
1407            shape: shape.into_iter().collect(),
1408            dims: dims.into_iter().collect(),
1409        }
1410    }
1411
1412    /// Return true when this fusion input is an identity view.
1413    ///
1414    /// # Examples
1415    ///
1416    /// ```rust
1417    /// use tenferro_tensor::backend::ElementwiseFusionInputView;
1418    ///
1419    /// assert!(ElementwiseFusionInputView::Identity.is_identity());
1420    /// ```
1421    pub fn is_identity(&self) -> bool {
1422        matches!(self, Self::Identity)
1423    }
1424}
1425
1426impl ElementwiseFusionInst {
1427    /// Build a backend elementwise fusion instruction.
1428    ///
1429    /// # Examples
1430    ///
1431    /// ```rust
1432    /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
1433    ///
1434    /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Add, vec![0, 1]);
1435    /// assert_eq!(inst.inputs(), &[0, 1]);
1436    /// ```
1437    pub fn new(op: ElementwiseFusionOp, inputs: Vec<usize>) -> Self {
1438        Self { op, inputs }
1439    }
1440
1441    /// Return the elementwise op executed by this instruction.
1442    ///
1443    /// # Examples
1444    ///
1445    /// ```rust
1446    /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
1447    ///
1448    /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0]);
1449    /// assert_eq!(inst.op(), ElementwiseFusionOp::Negate);
1450    /// ```
1451    pub fn op(&self) -> ElementwiseFusionOp {
1452        self.op
1453    }
1454
1455    /// Return this instruction's input value ids.
1456    ///
1457    /// # Examples
1458    ///
1459    /// ```rust
1460    /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
1461    ///
1462    /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Multiply, vec![2, 0]);
1463    /// assert_eq!(inst.inputs(), &[2, 0]);
1464    /// ```
1465    pub fn inputs(&self) -> &[usize] {
1466        &self.inputs
1467    }
1468}
1469
1470/// Runtime operation selected by [`TensorElementwise::elementwise_read_into`].
1471#[non_exhaustive]
1472#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1473pub enum ElementwiseReadOp {
1474    /// Binary addition.
1475    Add,
1476    /// Binary subtraction.
1477    Subtract,
1478    /// Binary multiplication.
1479    Multiply,
1480    /// Unary negation.
1481    Negate,
1482    /// Unary conjugation.
1483    Conj,
1484    /// Binary division.
1485    Divide,
1486}
1487
1488impl ElementwiseReadOp {
1489    #[doc(hidden)]
1490    pub fn label(self) -> &'static str {
1491        match self {
1492            Self::Add => "add",
1493            Self::Subtract => "sub",
1494            Self::Multiply => "mul",
1495            Self::Negate => "neg",
1496            Self::Conj => "conj",
1497            Self::Divide => "div",
1498        }
1499    }
1500
1501    #[doc(hidden)]
1502    pub fn arity(self) -> usize {
1503        match self {
1504            Self::Negate | Self::Conj => 1,
1505            Self::Add | Self::Subtract | Self::Multiply | Self::Divide => 2,
1506        }
1507    }
1508}
1509
1510#[derive(Clone, Copy, Debug)]
1511enum StorageIdentity {
1512    Host {
1513        start: usize,
1514        end: usize,
1515    },
1516    Backend {
1517        domain: Option<AllocationDomainId>,
1518        allocation: Option<AllocationId>,
1519        family: &'static str,
1520        object: usize,
1521    },
1522}
1523
1524fn host_storage_identity<T>(data: &[T]) -> StorageIdentity {
1525    let start = data.as_ptr() as usize;
1526    let bytes = std::mem::size_of_val(data);
1527    StorageIdentity::Host {
1528        start,
1529        end: start.saturating_add(bytes),
1530    }
1531}
1532
1533fn backend_storage_identity<T: 'static>(buffer: &dyn crate::BackendStorage<T>) -> StorageIdentity {
1534    StorageIdentity::Backend {
1535        domain: buffer.allocation_domain(),
1536        allocation: buffer.allocation_id(),
1537        family: buffer.backend_family(),
1538        // INVARIANT: every backend buffer is borrowed from the single Box-owned
1539        // root allocation; the data pointer of this trait object is stable for
1540        // that owner and is used only as a fallback when provider identity is
1541        // unavailable.
1542        object: buffer as *const dyn crate::BackendStorage<T> as *const () as usize,
1543    }
1544}
1545
1546fn typed_tensor_storage_identity<T: crate::TensorScalar>(
1547    tensor: &TypedTensor<T>,
1548) -> crate::Result<StorageIdentity> {
1549    if tensor.backend_buffer().is_some() {
1550        let buffer = tensor.backend_buffer().ok_or_else(|| {
1551            crate::Error::runtime_state("typed_tensor_storage_identity", "backend buffer missing")
1552        })?;
1553        Ok(backend_storage_identity(buffer))
1554    } else {
1555        Ok(host_storage_identity(tensor.host_data()?))
1556    }
1557}
1558
1559fn typed_view_storage_identity<T: crate::TensorScalar + 'static>(
1560    view: &TypedTensorView<'_, T>,
1561) -> crate::Result<StorageIdentity> {
1562    match view.backend_buffer() {
1563        Some(buffer) => Ok(backend_storage_identity(buffer)),
1564        None => view.host_storage().map(host_storage_identity),
1565    }
1566}
1567
1568fn tensor_read_storage_identity(input: &TensorRead<'_>) -> crate::Result<StorageIdentity> {
1569    macro_rules! typed_identity {
1570        ($value:expr) => {
1571            match $value {
1572                Tensor::F32(value) => typed_tensor_storage_identity(value),
1573                Tensor::F64(value) => typed_tensor_storage_identity(value),
1574                Tensor::I32(value) => typed_tensor_storage_identity(value),
1575                Tensor::I64(value) => typed_tensor_storage_identity(value),
1576                Tensor::Bool(value) => typed_tensor_storage_identity(value),
1577                Tensor::C32(value) => typed_tensor_storage_identity(value),
1578                Tensor::C64(value) => typed_tensor_storage_identity(value),
1579            }
1580        };
1581    }
1582    macro_rules! view_identity {
1583        ($value:expr) => {
1584            match $value {
1585                TensorView::F32(value) => typed_view_storage_identity(value),
1586                TensorView::F64(value) => typed_view_storage_identity(value),
1587                TensorView::I32(value) => typed_view_storage_identity(value),
1588                TensorView::I64(value) => typed_view_storage_identity(value),
1589                TensorView::Bool(value) => typed_view_storage_identity(value),
1590                TensorView::C32(value) => typed_view_storage_identity(value),
1591                TensorView::C64(value) => typed_view_storage_identity(value),
1592            }
1593        };
1594    }
1595
1596    match input {
1597        TensorRead::Tensor(tensor) => typed_identity!(tensor),
1598        TensorRead::View(view) => view_identity!(view),
1599    }
1600}
1601
1602fn storage_overlaps(lhs: StorageIdentity, rhs: StorageIdentity) -> bool {
1603    match (lhs, rhs) {
1604        (
1605            StorageIdentity::Host {
1606                start: lhs_start,
1607                end: lhs_end,
1608            },
1609            StorageIdentity::Host {
1610                start: rhs_start,
1611                end: rhs_end,
1612            },
1613        ) => lhs_start < rhs_end && rhs_start < lhs_end,
1614        (
1615            StorageIdentity::Backend {
1616                domain: lhs_domain,
1617                allocation: lhs_allocation,
1618                family: lhs_family,
1619                object: lhs_object,
1620            },
1621            StorageIdentity::Backend {
1622                domain: rhs_domain,
1623                allocation: rhs_allocation,
1624                family: rhs_family,
1625                object: rhs_object,
1626            },
1627        ) => {
1628            lhs_object == rhs_object
1629                || matches!(
1630                    (lhs_domain, rhs_domain, lhs_allocation, rhs_allocation),
1631                    (Some(lhs_domain), Some(rhs_domain), Some(lhs), Some(rhs))
1632                        if lhs_domain == rhs_domain && lhs == rhs
1633                )
1634                || matches!(
1635                    (lhs_domain, rhs_domain, lhs_allocation, rhs_allocation),
1636                    (None, None, Some(lhs), Some(rhs)) if lhs_family == rhs_family && lhs == rhs
1637                )
1638        }
1639        _ => false,
1640    }
1641}
1642
1643/// Validate that a caller-owned destination does not overlap any read input.
1644///
1645/// The check is intentionally conservative for host views: two views backed by
1646/// the same host allocation are treated as overlapping because the allocation
1647/// identity is the only stable boundary contract available to erased backend
1648/// code. Backend allocations use their domain/allocation identity when the
1649/// provider exposes it.
1650///
1651/// # Errors
1652///
1653/// Returns `tenferro_tensor_core::ValidationError::InvalidArgument` when the
1654/// destination storage overlaps an input, or `Error::RuntimeState` when
1655/// storage identity cannot be established safely.
1656///
1657/// # Examples
1658///
1659/// ```rust
1660/// use tenferro_tensor::{Tensor, TensorRead, TensorWrite};
1661/// use tenferro_tensor::backend::validate_read_into_destination;
1662///
1663/// let input = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
1664/// let mut output = Tensor::from_vec_col_major(vec![1], vec![0.0_f64])?;
1665/// validate_read_into_destination(
1666///     "example",
1667///     &[TensorRead::from_tensor(&input)],
1668///     &TensorWrite::from_tensor(&mut output),
1669/// )?;
1670/// # Ok::<(), tenferro_tensor::Error>(())
1671/// ```
1672pub fn validate_read_into_destination(
1673    op: &'static str,
1674    inputs: &[TensorRead<'_>],
1675    out: &TensorWrite<'_>,
1676) -> crate::Result<()> {
1677    let output_identity = tensor_read_storage_identity(&out.as_read())?;
1678    for (index, input) in inputs.iter().enumerate() {
1679        if storage_overlaps(tensor_read_storage_identity(input)?, output_identity) {
1680            return Err(Error::invalid_argument(
1681                op,
1682                "out",
1683                format!("destination storage overlaps input {index}"),
1684            ));
1685        }
1686    }
1687    Ok(())
1688}
1689
1690/// Execute a read-into elementwise operation through a backend's allocating operations.
1691///
1692/// Backend implementations use this helper when their device-native operation
1693/// APIs already provide the correct placement and copy semantics. It deliberately
1694/// does not inspect or transfer storage across devices.
1695///
1696/// # Errors
1697///
1698/// Returns [`Error::Validation`] for wrong arity or overlapping output storage,
1699/// and propagates the selected backend operation and copy errors.
1700#[doc(hidden)]
1701pub fn elementwise_read_into_via_allocating_ops<B: TensorElementwise + ?Sized>(
1702    backend: &mut B,
1703    op: ElementwiseReadOp,
1704    inputs: &[TensorRead<'_>],
1705    out: TensorWrite<'_>,
1706) -> crate::Result<()> {
1707    if inputs.len() != op.arity() {
1708        return Err(Error::invalid_argument(
1709            op.label(),
1710            "inputs",
1711            format!("expected {} inputs, got {}", op.arity(), inputs.len()),
1712        ));
1713    }
1714    validate_read_into_destination(op.label(), inputs, &out)?;
1715    let result = match op {
1716        ElementwiseReadOp::Add => backend.add_read(inputs[0].clone(), inputs[1].clone())?,
1717        ElementwiseReadOp::Subtract => backend.sub_read(inputs[0].clone(), inputs[1].clone())?,
1718        ElementwiseReadOp::Multiply => backend.mul_read(inputs[0].clone(), inputs[1].clone())?,
1719        ElementwiseReadOp::Negate => backend.neg_read(inputs[0].clone())?,
1720        ElementwiseReadOp::Conj => backend.conj_read(inputs[0].clone())?,
1721        ElementwiseReadOp::Divide => backend.div_read(inputs[0].clone(), inputs[1].clone())?,
1722    };
1723    backend.copy_read_into(TensorRead::from_tensor(&result), out)
1724}
1725
1726/// Elementwise tensor operations.
1727///
1728/// # Examples
1729///
1730/// ```rust
1731/// use tenferro_tensor::TensorElementwise;
1732///
1733/// fn accepts_elementwise<B: TensorElementwise>(_backend: &mut B) {}
1734/// ```
1735pub trait TensorElementwise: TensorStructural {
1736    /// Execute an elementwise operation into caller-owned storage.
1737    ///
1738    /// Every backend must implement this hook with its explicit execution
1739    /// context, placement checks, and output-storage policy. The hook is the
1740    /// backend boundary for borrowed inputs and caller-owned output; it must not
1741    /// silently transfer data between devices.
1742    ///
1743    /// # Examples
1744    ///
1745    /// ```rust
1746    /// use tenferro_tensor::{ElementwiseReadOp, TensorElementwise, TensorRead, TensorWrite};
1747    ///
1748    /// fn run_into<B: TensorElementwise>(
1749    ///     backend: &mut B,
1750    ///     input: TensorRead<'_>,
1751    ///     out: TensorWrite<'_>,
1752    /// ) -> tenferro_tensor::Result<()> {
1753    ///     backend.elementwise_read_into(ElementwiseReadOp::Conj, &[input], out)
1754    /// }
1755    /// ```
1756    ///
1757    /// # Errors
1758    ///
1759    /// Returns [`crate::Error::Validation`] for invalid arity, metadata, or
1760    /// overlapping storage. Backend-specific placement, unsupported-operation,
1761    /// and execution errors are returned unchanged by the implementation.
1762    fn elementwise_read_into(
1763        &mut self,
1764        op: ElementwiseReadOp,
1765        inputs: &[TensorRead<'_>],
1766        out: TensorWrite<'_>,
1767    ) -> crate::Result<()>;
1768
1769    /// # Errors
1770    ///
1771    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1772    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1773    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1774    /// backend execution or storage access cannot provide the requested result.
1775    fn add(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1776
1777    /// Elementwise addition accepting either owned tensors or borrowed views.
1778    ///
1779    /// Backends that implement this method must not silently move data across
1780    /// devices. A backend that cannot consume views should return an explicit
1781    /// backend error rather than materializing or transferring implicitly.
1782    ///
1783    /// # Examples
1784    ///
1785    /// ```rust
1786    /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
1787    ///
1788    /// fn add_owned<B: TensorElementwise>(
1789    ///     backend: &mut B,
1790    ///     lhs: &Tensor,
1791    ///     rhs: &Tensor,
1792    /// ) -> tenferro_tensor::Result<Tensor> {
1793    ///     backend.add_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
1794    /// }
1795    /// ```
1796    /// # Errors
1797    ///
1798    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1799    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1800    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1801    /// backend execution or storage access cannot provide the requested result.
1802    fn add_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1803        self.add(read_tensor("add", lhs)?, read_tensor("add", rhs)?)
1804    }
1805
1806    /// Overwrite caller-provided output with elementwise addition.
1807    ///
1808    /// `_into` methods never accumulate into the previous output value.
1809    ///
1810    /// # Examples
1811    ///
1812    /// ```rust
1813    /// use tenferro_tensor::{Tensor, TensorElementwise, TensorWrite};
1814    ///
1815    /// fn add_into<B: TensorElementwise>(
1816    ///     backend: &mut B,
1817    ///     lhs: &Tensor,
1818    ///     rhs: &Tensor,
1819    ///     mut out: Tensor,
1820    /// ) -> tenferro_tensor::Result<Tensor> {
1821    ///     backend.add_into(lhs, rhs, TensorWrite::from_tensor(&mut out))?;
1822    ///     Ok(out)
1823    /// }
1824    /// ```
1825    /// # Errors
1826    ///
1827    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1828    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1829    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1830    /// backend execution or storage access cannot provide the requested result.
1831    fn add_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1832        self.add_read_into(
1833            TensorRead::from_tensor(lhs),
1834            TensorRead::from_tensor(rhs),
1835            out,
1836        )
1837    }
1838
1839    /// Overwrite caller-provided output with elementwise addition from reads.
1840    ///
1841    /// # Examples
1842    ///
1843    /// ```rust
1844    /// use tenferro_tensor::{TensorElementwise, TensorRead, TensorWrite};
1845    ///
1846    /// fn add_read_into<B: TensorElementwise>(
1847    ///     backend: &mut B,
1848    ///     lhs: TensorRead<'_>,
1849    ///     rhs: TensorRead<'_>,
1850    ///     out: TensorWrite<'_>,
1851    /// ) -> tenferro_tensor::Result<()> {
1852    ///     backend.add_read_into(lhs, rhs, out)
1853    /// }
1854    /// ```
1855    /// # Errors
1856    ///
1857    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1858    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1859    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1860    /// backend execution or storage access cannot provide the requested result.
1861    fn add_read_into(
1862        &mut self,
1863        lhs: TensorRead<'_>,
1864        rhs: TensorRead<'_>,
1865        out: TensorWrite<'_>,
1866    ) -> crate::Result<()> {
1867        self.elementwise_read_into(ElementwiseReadOp::Add, &[lhs, rhs], out)
1868    }
1869
1870    /// # Errors
1871    ///
1872    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1873    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1874    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1875    /// backend execution or storage access cannot provide the requested result.
1876    fn sub(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1877
1878    /// Elementwise subtraction accepting either owned tensors or borrowed views.
1879    ///
1880    /// # Examples
1881    ///
1882    /// ```rust
1883    /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
1884    ///
1885    /// fn sub_owned<B: TensorElementwise>(
1886    ///     backend: &mut B,
1887    ///     lhs: &Tensor,
1888    ///     rhs: &Tensor,
1889    /// ) -> tenferro_tensor::Result<Tensor> {
1890    ///     backend.sub_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
1891    /// }
1892    /// ```
1893    /// # Errors
1894    ///
1895    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1896    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1897    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1898    /// backend execution or storage access cannot provide the requested result.
1899    fn sub_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1900        self.sub(read_tensor("sub", lhs)?, read_tensor("sub", rhs)?)
1901    }
1902
1903    /// Overwrite caller-provided output with elementwise subtraction.
1904    /// # Errors
1905    ///
1906    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1907    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1908    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1909    /// backend execution or storage access cannot provide the requested result.
1910    fn sub_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1911        self.sub_read_into(
1912            TensorRead::from_tensor(lhs),
1913            TensorRead::from_tensor(rhs),
1914            out,
1915        )
1916    }
1917
1918    /// Overwrite caller-provided output with elementwise subtraction from reads.
1919    /// # Errors
1920    ///
1921    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1922    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1923    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1924    /// backend execution or storage access cannot provide the requested result.
1925    fn sub_read_into(
1926        &mut self,
1927        lhs: TensorRead<'_>,
1928        rhs: TensorRead<'_>,
1929        out: TensorWrite<'_>,
1930    ) -> crate::Result<()> {
1931        self.elementwise_read_into(ElementwiseReadOp::Subtract, &[lhs, rhs], out)
1932    }
1933
1934    /// # Errors
1935    ///
1936    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1937    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1938    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1939    /// backend execution or storage access cannot provide the requested result.
1940    fn mul(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1941    /// # Errors
1942    ///
1943    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1944    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1945    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1946    /// backend execution or storage access cannot provide the requested result.
1947    fn mul_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1948        self.mul(read_tensor("mul", lhs)?, read_tensor("mul", rhs)?)
1949    }
1950
1951    /// Overwrite caller-provided output with elementwise multiplication.
1952    /// # Errors
1953    ///
1954    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1955    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1956    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1957    /// backend execution or storage access cannot provide the requested result.
1958    fn mul_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1959        self.mul_read_into(
1960            TensorRead::from_tensor(lhs),
1961            TensorRead::from_tensor(rhs),
1962            out,
1963        )
1964    }
1965
1966    /// Overwrite caller-provided output with elementwise multiplication from reads.
1967    /// # Errors
1968    ///
1969    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1970    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1971    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1972    /// backend execution or storage access cannot provide the requested result.
1973    fn mul_read_into(
1974        &mut self,
1975        lhs: TensorRead<'_>,
1976        rhs: TensorRead<'_>,
1977        out: TensorWrite<'_>,
1978    ) -> crate::Result<()> {
1979        self.elementwise_read_into(ElementwiseReadOp::Multiply, &[lhs, rhs], out)
1980    }
1981
1982    /// # Errors
1983    ///
1984    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1985    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1986    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1987    /// backend execution or storage access cannot provide the requested result.
1988    fn neg(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1989    /// # Errors
1990    ///
1991    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1992    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1993    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1994    /// backend execution or storage access cannot provide the requested result.
1995    fn neg_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1996        self.neg(read_tensor("neg", input)?)
1997    }
1998
1999    /// Overwrite caller-provided output with elementwise negation.
2000    /// # Errors
2001    ///
2002    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2003    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2004    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2005    /// backend execution or storage access cannot provide the requested result.
2006    fn neg_into(&mut self, input: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
2007        self.neg_read_into(TensorRead::from_tensor(input), out)
2008    }
2009
2010    /// Overwrite caller-provided output with elementwise negation from a read.
2011    /// # Errors
2012    ///
2013    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2014    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2015    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2016    /// backend execution or storage access cannot provide the requested result.
2017    fn neg_read_into(&mut self, input: TensorRead<'_>, out: TensorWrite<'_>) -> crate::Result<()> {
2018        self.elementwise_read_into(ElementwiseReadOp::Negate, &[input], out)
2019    }
2020
2021    /// # Errors
2022    ///
2023    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2024    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2025    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2026    /// backend execution or storage access cannot provide the requested result.
2027    fn conj(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2028    /// # Errors
2029    ///
2030    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2031    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2032    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2033    /// backend execution or storage access cannot provide the requested result.
2034    fn conj_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2035        self.conj(read_tensor("conj", input)?)
2036    }
2037
2038    /// Overwrite caller-provided output with elementwise conjugation.
2039    /// # Errors
2040    ///
2041    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2042    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2043    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2044    /// backend execution or storage access cannot provide the requested result.
2045    fn conj_into(&mut self, input: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
2046        self.conj_read_into(TensorRead::from_tensor(input), out)
2047    }
2048
2049    /// Overwrite caller-provided output with elementwise conjugation from a read.
2050    /// # Errors
2051    ///
2052    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2053    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2054    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2055    /// backend execution or storage access cannot provide the requested result.
2056    fn conj_read_into(&mut self, input: TensorRead<'_>, out: TensorWrite<'_>) -> crate::Result<()> {
2057        self.elementwise_read_into(ElementwiseReadOp::Conj, &[input], out)
2058    }
2059
2060    /// # Errors
2061    ///
2062    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2063    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2064    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2065    /// backend execution or storage access cannot provide the requested result.
2066    fn div(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2067    /// # Errors
2068    ///
2069    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2070    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2071    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2072    /// backend execution or storage access cannot provide the requested result.
2073    fn div_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2074        self.div(read_tensor("div", lhs)?, read_tensor("div", rhs)?)
2075    }
2076
2077    /// Overwrite caller-provided output with elementwise division.
2078    /// # Errors
2079    ///
2080    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2081    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2082    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2083    /// backend execution or storage access cannot provide the requested result.
2084    fn div_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
2085        self.div_read_into(
2086            TensorRead::from_tensor(lhs),
2087            TensorRead::from_tensor(rhs),
2088            out,
2089        )
2090    }
2091
2092    /// Overwrite caller-provided output with elementwise division from reads.
2093    /// # Errors
2094    ///
2095    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2096    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2097    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2098    /// backend execution or storage access cannot provide the requested result.
2099    fn div_read_into(
2100        &mut self,
2101        lhs: TensorRead<'_>,
2102        rhs: TensorRead<'_>,
2103        out: TensorWrite<'_>,
2104    ) -> crate::Result<()> {
2105        self.elementwise_read_into(ElementwiseReadOp::Divide, &[lhs, rhs], out)
2106    }
2107
2108    /// Elementwise remainder.
2109    ///
2110    /// The default is an explicit unsupported error so backend implementors can
2111    /// opt in without silent fallback.
2112    ///
2113    /// # Examples
2114    ///
2115    /// ```rust
2116    /// use tenferro_tensor::{Tensor, TensorElementwise};
2117    ///
2118    /// fn rem_owned<B: TensorElementwise>(
2119    ///     backend: &mut B,
2120    ///     lhs: &Tensor,
2121    ///     rhs: &Tensor,
2122    /// ) -> tenferro_tensor::Result<Tensor> {
2123    ///     backend.rem(lhs, rhs)
2124    /// }
2125    /// ```
2126    /// # Errors
2127    ///
2128    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2129    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2130    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2131    /// backend execution or storage access cannot provide the requested result.
2132    fn rem(&mut self, lhs: &Tensor, _rhs: &Tensor) -> crate::Result<Tensor> {
2133        Err(crate::Error::unsupported(
2134            "rem",
2135            format!("backend does not implement rem for dtype {:?}", lhs.dtype()),
2136        ))
2137    }
2138
2139    /// Elementwise remainder accepting owned tensors or borrowed views.
2140    ///
2141    /// # Examples
2142    ///
2143    /// ```rust
2144    /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
2145    ///
2146    /// fn rem_read<B: TensorElementwise>(
2147    ///     backend: &mut B,
2148    ///     lhs: &Tensor,
2149    ///     rhs: &Tensor,
2150    /// ) -> tenferro_tensor::Result<Tensor> {
2151    ///     backend.rem_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
2152    /// }
2153    /// ```
2154    /// # Errors
2155    ///
2156    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2157    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2158    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2159    /// backend execution or storage access cannot provide the requested result.
2160    fn rem_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2161        self.rem(read_tensor("rem", lhs)?, read_tensor("rem", rhs)?)
2162    }
2163
2164    /// # Errors
2165    ///
2166    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2167    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2168    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2169    /// backend execution or storage access cannot provide the requested result.
2170    fn abs(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2171    /// # Errors
2172    ///
2173    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2174    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2175    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2176    /// backend execution or storage access cannot provide the requested result.
2177    fn abs_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2178        self.abs(read_tensor("abs", input)?)
2179    }
2180
2181    /// # Errors
2182    ///
2183    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2184    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2185    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2186    /// backend execution or storage access cannot provide the requested result.
2187    fn sign(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2188    /// # Errors
2189    ///
2190    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2191    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2192    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2193    /// backend execution or storage access cannot provide the requested result.
2194    fn sign_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2195        self.sign(read_tensor("sign", input)?)
2196    }
2197
2198    /// # Errors
2199    ///
2200    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2201    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2202    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2203    /// backend execution or storage access cannot provide the requested result.
2204    fn maximum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2205    /// # Errors
2206    ///
2207    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2208    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2209    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2210    /// backend execution or storage access cannot provide the requested result.
2211    fn maximum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2212        self.maximum(read_tensor("maximum", lhs)?, read_tensor("maximum", rhs)?)
2213    }
2214
2215    /// # Errors
2216    ///
2217    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2218    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2219    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2220    /// backend execution or storage access cannot provide the requested result.
2221    fn minimum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2222    /// # Errors
2223    ///
2224    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2225    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2226    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2227    /// backend execution or storage access cannot provide the requested result.
2228    fn minimum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2229        self.minimum(read_tensor("minimum", lhs)?, read_tensor("minimum", rhs)?)
2230    }
2231
2232    /// # Errors
2233    ///
2234    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2235    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2236    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2237    /// backend execution or storage access cannot provide the requested result.
2238    fn compare(&mut self, lhs: &Tensor, rhs: &Tensor, dir: &CompareDir) -> crate::Result<Tensor>;
2239    /// # Errors
2240    ///
2241    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2242    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2243    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2244    /// backend execution or storage access cannot provide the requested result.
2245    fn compare_read(
2246        &mut self,
2247        lhs: TensorRead<'_>,
2248        rhs: TensorRead<'_>,
2249        dir: &CompareDir,
2250    ) -> crate::Result<Tensor> {
2251        self.compare(
2252            read_tensor("compare", lhs)?,
2253            read_tensor("compare", rhs)?,
2254            dir,
2255        )
2256    }
2257
2258    /// # Errors
2259    ///
2260    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2261    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2262    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2263    /// backend execution or storage access cannot provide the requested result.
2264    fn select(
2265        &mut self,
2266        pred: &Tensor,
2267        on_true: &Tensor,
2268        on_false: &Tensor,
2269    ) -> crate::Result<Tensor>;
2270    /// # Errors
2271    ///
2272    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2273    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2274    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2275    /// backend execution or storage access cannot provide the requested result.
2276    fn select_read(
2277        &mut self,
2278        pred: TensorRead<'_>,
2279        on_true: TensorRead<'_>,
2280        on_false: TensorRead<'_>,
2281    ) -> crate::Result<Tensor> {
2282        self.select(
2283            read_tensor("select", pred)?,
2284            read_tensor("select", on_true)?,
2285            read_tensor("select", on_false)?,
2286        )
2287    }
2288
2289    /// # Errors
2290    ///
2291    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2292    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2293    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2294    /// backend execution or storage access cannot provide the requested result.
2295    fn clamp(&mut self, input: &Tensor, lower: &Tensor, upper: &Tensor) -> crate::Result<Tensor>;
2296    /// # Errors
2297    ///
2298    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2299    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2300    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2301    /// backend execution or storage access cannot provide the requested result.
2302    fn clamp_read(
2303        &mut self,
2304        input: TensorRead<'_>,
2305        lower: TensorRead<'_>,
2306        upper: TensorRead<'_>,
2307    ) -> crate::Result<Tensor> {
2308        self.clamp(
2309            read_tensor("clamp", input)?,
2310            read_tensor("clamp", lower)?,
2311            read_tensor("clamp", upper)?,
2312        )
2313    }
2314}
2315
2316/// Analytic unary and binary tensor operations.
2317///
2318/// # Examples
2319///
2320/// ```rust
2321/// use tenferro_tensor::TensorAnalytic;
2322///
2323/// fn accepts_analytic<B: TensorAnalytic>(_backend: &mut B) {}
2324/// ```
2325pub trait TensorAnalytic {
2326    /// # Errors
2327    ///
2328    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2329    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2330    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2331    /// backend execution or storage access cannot provide the requested result.
2332    fn exp(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2333    /// # Errors
2334    ///
2335    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2336    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2337    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2338    /// backend execution or storage access cannot provide the requested result.
2339    fn exp_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2340        self.exp(read_tensor("exp", input)?)
2341    }
2342
2343    /// # Errors
2344    ///
2345    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2346    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2347    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2348    /// backend execution or storage access cannot provide the requested result.
2349    fn log(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2350    /// # Errors
2351    ///
2352    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2353    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2354    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2355    /// backend execution or storage access cannot provide the requested result.
2356    fn log_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2357        self.log(read_tensor("log", input)?)
2358    }
2359
2360    /// # Errors
2361    ///
2362    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2363    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2364    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2365    /// backend execution or storage access cannot provide the requested result.
2366    fn sin(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2367    /// # Errors
2368    ///
2369    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2370    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2371    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2372    /// backend execution or storage access cannot provide the requested result.
2373    fn sin_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2374        self.sin(read_tensor("sin", input)?)
2375    }
2376
2377    /// # Errors
2378    ///
2379    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2380    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2381    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2382    /// backend execution or storage access cannot provide the requested result.
2383    fn cos(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2384    /// # Errors
2385    ///
2386    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2387    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2388    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2389    /// backend execution or storage access cannot provide the requested result.
2390    fn cos_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2391        self.cos(read_tensor("cos", input)?)
2392    }
2393
2394    /// # Errors
2395    ///
2396    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2397    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2398    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2399    /// backend execution or storage access cannot provide the requested result.
2400    fn tanh(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2401    /// # Errors
2402    ///
2403    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2404    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2405    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2406    /// backend execution or storage access cannot provide the requested result.
2407    fn tanh_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2408        self.tanh(read_tensor("tanh", input)?)
2409    }
2410
2411    /// # Errors
2412    ///
2413    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2414    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2415    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2416    /// backend execution or storage access cannot provide the requested result.
2417    fn sqrt(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2418    /// # Errors
2419    ///
2420    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2421    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2422    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2423    /// backend execution or storage access cannot provide the requested result.
2424    fn sqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2425        self.sqrt(read_tensor("sqrt", input)?)
2426    }
2427
2428    /// # Errors
2429    ///
2430    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2431    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2432    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2433    /// backend execution or storage access cannot provide the requested result.
2434    fn rsqrt(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2435    /// # Errors
2436    ///
2437    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2438    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2439    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2440    /// backend execution or storage access cannot provide the requested result.
2441    fn rsqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2442        self.rsqrt(read_tensor("rsqrt", input)?)
2443    }
2444
2445    /// # Errors
2446    ///
2447    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2448    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2449    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2450    /// backend execution or storage access cannot provide the requested result.
2451    fn pow(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2452    /// # Errors
2453    ///
2454    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2455    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2456    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2457    /// backend execution or storage access cannot provide the requested result.
2458    fn pow_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2459        self.pow(read_tensor("pow", lhs)?, read_tensor("pow", rhs)?)
2460    }
2461
2462    /// # Errors
2463    ///
2464    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2465    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2466    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2467    /// backend execution or storage access cannot provide the requested result.
2468    fn expm1(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2469    /// # Errors
2470    ///
2471    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2472    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2473    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2474    /// backend execution or storage access cannot provide the requested result.
2475    fn expm1_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2476        self.expm1(read_tensor("expm1", input)?)
2477    }
2478
2479    /// # Errors
2480    ///
2481    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2482    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2483    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2484    /// backend execution or storage access cannot provide the requested result.
2485    fn log1p(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2486    /// # Errors
2487    ///
2488    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2489    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2490    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2491    /// backend execution or storage access cannot provide the requested result.
2492    fn log1p_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2493        self.log1p(read_tensor("log1p", input)?)
2494    }
2495}
2496
2497/// Shape, layout, and dtype transformation operations.
2498///
2499/// # Examples
2500///
2501/// ```rust
2502/// use tenferro_tensor::TensorStructural;
2503///
2504/// fn accepts_structural<B: TensorStructural>(_backend: &mut B) {}
2505/// ```
2506pub trait TensorStructural {
2507    /// Materialize an owned tensor or borrowed view into fresh compact storage.
2508    ///
2509    /// The result has the input's shape and dtype, uses compact column-major
2510    /// layout, and remains in the input's placement. This operation is a
2511    /// same-placement canonicalization boundary, never an implicit host/device
2512    /// transfer. The conservative default accepts only compact host-owned
2513    /// tensors and clones them; it rejects views, backend buffers, and device
2514    /// placement because only an owning backend can materialize those safely.
2515    ///
2516    /// Backend overrides may accept strided views. CUDA accepts numeric and
2517    /// complex views on its active device, including arbitrary valid strides,
2518    /// but currently reports an explicit unsupported-dtype error for `Bool`.
2519    ///
2520    /// # Examples
2521    ///
2522    /// ```rust
2523    /// use tenferro_tensor::{DType, Tensor, TensorRead, TensorStructural};
2524    ///
2525    /// struct HostDefaults;
2526    /// impl TensorStructural for HostDefaults {
2527    ///     fn transpose(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2528    ///     fn reshape(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2529    ///     fn broadcast_in_dim(&mut self, _: &Tensor, _: &[usize], _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2530    ///     fn cast(&mut self, _: &Tensor, _: DType) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2531    ///     fn extract_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2532    ///     fn embed_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2533    ///     fn tril(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2534    ///     fn triu(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2535    /// }
2536    ///
2537    /// let input = Tensor::from_vec_col_major(vec![2], vec![1_i32, 2])?;
2538    /// let mut backend = HostDefaults;
2539    /// let structural: &mut dyn TensorStructural = &mut backend;
2540    /// let output = structural.to_contiguous_read(TensorRead::from_tensor(&input))?;
2541    /// assert_eq!(output.shape(), &[2]);
2542    /// assert_eq!(output.as_slice::<i32>()?, &[1, 2]);
2543    /// # Ok::<(), tenferro_tensor::Error>(())
2544    /// ```
2545    /// # Errors
2546    ///
2547    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2548    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2549    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2550    /// backend execution or storage access cannot provide the requested result.
2551    fn to_contiguous_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2552        match input {
2553            TensorRead::Tensor(input) => {
2554                if input.is_backend_buffer()
2555                    || !matches!(
2556                        input.placement().memory_kind,
2557                        crate::MemoryKind::PinnedHost | crate::MemoryKind::UnpinnedHost
2558                    )
2559                {
2560                    return Err(crate::Error::runtime_state(
2561                        "to_contiguous_read",
2562                        "default materialization accepts only host-owned tensors; use the storage's owning backend",
2563                    ));
2564                }
2565                input.duplicate()
2566            }
2567            TensorRead::View(view) => {
2568                if view.backend_family().is_some()
2569                    || !matches!(
2570                        view.placement().memory_kind,
2571                        crate::MemoryKind::PinnedHost | crate::MemoryKind::UnpinnedHost
2572                    )
2573                {
2574                    return Err(crate::Error::runtime_state(
2575                        "to_contiguous_read",
2576                        "default materialization accepts only host-owned tensors; use the storage's owning backend",
2577                    ));
2578                }
2579                view.duplicate()
2580            }
2581        }
2582    }
2583
2584    /// Overwrite caller-provided storage from a readable tensor or view.
2585    ///
2586    /// Source and destination must have identical dtype and shape and belong to
2587    /// the executing backend's placement. The destination is not resized, and
2588    /// every logical destination element is overwritten without reading its old
2589    /// value. Source and destination allocations must not alias. Implementations
2590    /// must not materialize through host memory or perform an implicit transfer.
2591    ///
2592    /// CPU accepts arbitrary valid source and destination strides and performs
2593    /// no tensor allocation. CUDA currently accepts only a compact column-major
2594    /// source with offset zero covering its full allocation; CUDA destinations
2595    /// may be arbitrary valid non-overlapping views. CUDA rejects aliased
2596    /// allocations and currently reports an explicit unsupported-dtype error
2597    /// for `Bool`. The conservative default is explicitly unsupported.
2598    ///
2599    /// # Examples
2600    ///
2601    /// ```rust
2602    /// use tenferro_tensor::{DType, Tensor, TensorRead, TensorStructural, TensorWrite};
2603    ///
2604    /// struct ConservativeDefaults;
2605    /// impl TensorStructural for ConservativeDefaults {
2606    ///     fn transpose(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2607    ///     fn reshape(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2608    ///     fn broadcast_in_dim(&mut self, _: &Tensor, _: &[usize], _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2609    ///     fn cast(&mut self, _: &Tensor, _: DType) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2610    ///     fn extract_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2611    ///     fn embed_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2612    ///     fn tril(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2613    ///     fn triu(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2614    /// }
2615    ///
2616    /// let src = Tensor::from_vec_col_major(vec![2], vec![1_i32, 2])?;
2617    /// let mut dst = Tensor::from_vec_col_major(vec![2], vec![0_i32, 0])?;
2618    /// let mut backend = ConservativeDefaults;
2619    /// let structural: &mut dyn TensorStructural = &mut backend;
2620    /// let error = structural.copy_read_into(
2621    ///     TensorRead::from_tensor(&src),
2622    ///     TensorWrite::from_tensor(&mut dst),
2623    /// ).unwrap_err();
2624    /// assert!(error.to_string().contains("unsupported"));
2625    /// assert_eq!(dst.as_slice::<i32>()?, &[0, 0]);
2626    /// # Ok::<(), tenferro_tensor::Error>(())
2627    /// ```
2628    /// # Errors
2629    ///
2630    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2631    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2632    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2633    /// backend execution or storage access cannot provide the requested result.
2634    fn copy_read_into(&mut self, _src: TensorRead<'_>, _dst: TensorWrite<'_>) -> crate::Result<()> {
2635        Err(crate::Error::unsupported(
2636            "copy_read_into",
2637            "backend-owned runtime copy is unsupported by this backend",
2638        ))
2639    }
2640
2641    /// # Errors
2642    ///
2643    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2644    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2645    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2646    /// backend execution or storage access cannot provide the requested result.
2647    fn transpose(&mut self, input: &Tensor, perm: &[usize]) -> crate::Result<Tensor>;
2648    /// # Errors
2649    ///
2650    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2651    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2652    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2653    /// backend execution or storage access cannot provide the requested result.
2654    fn transpose_read(&mut self, input: TensorRead<'_>, perm: &[usize]) -> crate::Result<Tensor> {
2655        self.transpose(read_tensor("transpose", input)?, perm)
2656    }
2657
2658    /// # Errors
2659    ///
2660    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2661    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2662    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2663    /// backend execution or storage access cannot provide the requested result.
2664    fn reshape(&mut self, input: &Tensor, shape: &[usize]) -> crate::Result<Tensor>;
2665    /// # Errors
2666    ///
2667    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2668    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2669    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2670    /// backend execution or storage access cannot provide the requested result.
2671    fn reshape_read(&mut self, input: TensorRead<'_>, shape: &[usize]) -> crate::Result<Tensor> {
2672        self.reshape(read_tensor("reshape", input)?, shape)
2673    }
2674
2675    /// # Errors
2676    ///
2677    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2678    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2679    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2680    /// backend execution or storage access cannot provide the requested result.
2681    fn broadcast_in_dim(
2682        &mut self,
2683        input: &Tensor,
2684        shape: &[usize],
2685        dims: &[usize],
2686    ) -> crate::Result<Tensor>;
2687    /// # Errors
2688    ///
2689    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2690    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2691    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2692    /// backend execution or storage access cannot provide the requested result.
2693    fn broadcast_in_dim_read(
2694        &mut self,
2695        input: TensorRead<'_>,
2696        shape: &[usize],
2697        dims: &[usize],
2698    ) -> crate::Result<Tensor> {
2699        self.broadcast_in_dim(read_tensor("broadcast_in_dim", input)?, shape, dims)
2700    }
2701
2702    /// Cast a tensor to another dtype using explicit dtype projection.
2703    ///
2704    /// Backends may truncate, narrow precision, project complex values, or use
2705    /// boolean truthiness according to their documented cast support.
2706    ///
2707    /// # Examples
2708    ///
2709    /// ```rust
2710    /// use tenferro_tensor::{DType, Tensor, TensorStructural};
2711    ///
2712    /// fn cast_to_i32<B: TensorStructural>(
2713    ///     backend: &mut B,
2714    ///     input: &Tensor,
2715    /// ) -> tenferro_tensor::Result<Tensor> {
2716    ///     backend.cast(input, DType::I32)
2717    /// }
2718    /// ```
2719    /// # Errors
2720    ///
2721    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2722    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2723    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2724    /// backend execution or storage access cannot provide the requested result.
2725    fn cast(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor>;
2726
2727    /// Convert a tensor to another dtype using checked dtype conversion.
2728    ///
2729    /// `convert` accepts only conversions allowed by tenferro's dtype-promotion
2730    /// lattice. Use [`TensorStructural::cast`] for explicit lossy projection.
2731    ///
2732    /// # Examples
2733    ///
2734    /// ```rust
2735    /// use tenferro_tensor::{DType, Tensor, TensorStructural};
2736    ///
2737    /// fn convert_to_f64<B: TensorStructural>(
2738    ///     backend: &mut B,
2739    ///     input: &Tensor,
2740    /// ) -> tenferro_tensor::Result<Tensor> {
2741    ///     backend.convert(input, DType::F64)
2742    /// }
2743    /// ```
2744    /// # Errors
2745    ///
2746    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2747    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2748    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2749    /// backend execution or storage access cannot provide the requested result.
2750    fn convert(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor> {
2751        validate_convert_dtype("convert", input.dtype(), to)?;
2752        self.cast(input, to)
2753    }
2754
2755    /// # Errors
2756    ///
2757    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2758    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2759    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2760    /// backend execution or storage access cannot provide the requested result.
2761    fn extract_diagonal(
2762        &mut self,
2763        input: &Tensor,
2764        axis_a: usize,
2765        axis_b: usize,
2766    ) -> crate::Result<Tensor>;
2767    /// # Errors
2768    ///
2769    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2770    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2771    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2772    /// backend execution or storage access cannot provide the requested result.
2773    fn embed_diagonal(
2774        &mut self,
2775        input: &Tensor,
2776        axis_a: usize,
2777        axis_b: usize,
2778    ) -> crate::Result<Tensor>;
2779    /// # Errors
2780    ///
2781    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2782    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2783    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2784    /// backend execution or storage access cannot provide the requested result.
2785    fn tril(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor>;
2786    /// # Errors
2787    ///
2788    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2789    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2790    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2791    /// backend execution or storage access cannot provide the requested result.
2792    fn triu(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor>;
2793}
2794
2795/// Reduction operations.
2796///
2797/// Reducing over an axis whose extent is zero returns an error for every
2798/// reduction operation. Passing an empty `axes` slice is a no-op for the public
2799/// reductions and returns the input values unchanged. Internal mapped
2800/// reductions document their own empty-axis semantics.
2801///
2802/// # Examples
2803///
2804/// ```rust
2805/// use tenferro_tensor::TensorReduction;
2806///
2807/// fn accepts_reduction<B: TensorReduction>(_backend: &mut B) {}
2808/// ```
2809pub trait TensorReduction {
2810    /// # Errors
2811    ///
2812    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2813    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2814    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2815    /// backend execution or storage access cannot provide the requested result.
2816    fn reduce_sum(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2817
2818    /// Sum elements across axes from an owned tensor or borrowed view.
2819    ///
2820    /// # Examples
2821    ///
2822    /// ```rust
2823    /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2824    ///
2825    /// fn sum_owned<B: TensorReduction>(
2826    ///     backend: &mut B,
2827    ///     input: &Tensor,
2828    /// ) -> tenferro_tensor::Result<Tensor> {
2829    ///     backend.reduce_sum_read(TensorRead::from_tensor(input), &[0])
2830    /// }
2831    /// ```
2832    /// # Errors
2833    ///
2834    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2835    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2836    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2837    /// backend execution or storage access cannot provide the requested result.
2838    fn reduce_sum_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
2839        match input.as_tensor() {
2840            Some(input) => self.reduce_sum(input, axes),
2841            None => Err(crate::Error::unsupported(
2842                "reduce_sum",
2843                "backend does not accept borrowed tensor views at this execution boundary",
2844            )),
2845        }
2846    }
2847
2848    /// Sum elementwise squares across axes.
2849    ///
2850    /// This execution hook is used by composite operations that avoid a
2851    /// materialized square. Empty axes produce an elementwise square. Backends
2852    /// that support this optimized path must override the hook directly.
2853    ///
2854    /// # Errors
2855    ///
2856    /// Returns the typed validation, unsupported, runtime-state, or backend
2857    /// error produced by multiplication or reduction.
2858    #[doc(hidden)]
2859    fn reduce_sum_squares_read(
2860        &mut self,
2861        _input: TensorRead<'_>,
2862        _axes: &[usize],
2863    ) -> crate::Result<Tensor> {
2864        Err(crate::Error::unsupported(
2865            "reduce_sum_squares",
2866            "backend does not implement fused sum-of-squares reduction",
2867        ))
2868    }
2869
2870    /// # Errors
2871    ///
2872    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2873    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2874    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2875    /// backend execution or storage access cannot provide the requested result.
2876    fn reduce_prod(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2877
2878    /// Multiply elements across axes from an owned tensor or borrowed view.
2879    ///
2880    /// # Examples
2881    ///
2882    /// ```rust
2883    /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2884    ///
2885    /// fn prod_owned<B: TensorReduction>(
2886    ///     backend: &mut B,
2887    ///     input: &Tensor,
2888    /// ) -> tenferro_tensor::Result<Tensor> {
2889    ///     backend.reduce_prod_read(TensorRead::from_tensor(input), &[0])
2890    /// }
2891    /// ```
2892    /// # Errors
2893    ///
2894    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2895    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2896    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2897    /// backend execution or storage access cannot provide the requested result.
2898    fn reduce_prod_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
2899        match input.as_tensor() {
2900            Some(input) => self.reduce_prod(input, axes),
2901            None => Err(crate::Error::unsupported(
2902                "reduce_prod",
2903                "backend does not accept borrowed tensor views at this execution boundary",
2904            )),
2905        }
2906    }
2907
2908    /// # Errors
2909    ///
2910    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2911    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2912    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2913    /// backend execution or storage access cannot provide the requested result.
2914    fn reduce_max(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2915
2916    /// Take maximum values across axes from an owned tensor or borrowed view.
2917    ///
2918    /// # Examples
2919    ///
2920    /// ```rust
2921    /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2922    ///
2923    /// fn max_owned<B: TensorReduction>(
2924    ///     backend: &mut B,
2925    ///     input: &Tensor,
2926    /// ) -> tenferro_tensor::Result<Tensor> {
2927    ///     backend.reduce_max_read(TensorRead::from_tensor(input), &[0])
2928    /// }
2929    /// ```
2930    /// # Errors
2931    ///
2932    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2933    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2934    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2935    /// backend execution or storage access cannot provide the requested result.
2936    fn reduce_max_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
2937        match input.as_tensor() {
2938            Some(input) => self.reduce_max(input, axes),
2939            None => Err(crate::Error::unsupported(
2940                "reduce_max",
2941                "backend does not accept borrowed tensor views at this execution boundary",
2942            )),
2943        }
2944    }
2945
2946    /// # Errors
2947    ///
2948    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2949    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2950    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2951    /// backend execution or storage access cannot provide the requested result.
2952    fn reduce_min(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2953
2954    /// Take minimum values across axes from an owned tensor or borrowed view.
2955    ///
2956    /// # Examples
2957    ///
2958    /// ```rust
2959    /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2960    ///
2961    /// fn min_owned<B: TensorReduction>(
2962    ///     backend: &mut B,
2963    ///     input: &Tensor,
2964    /// ) -> tenferro_tensor::Result<Tensor> {
2965    ///     backend.reduce_min_read(TensorRead::from_tensor(input), &[0])
2966    /// }
2967    /// ```
2968    /// # Errors
2969    ///
2970    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2971    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2972    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2973    /// backend execution or storage access cannot provide the requested result.
2974    fn reduce_min_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
2975        match input.as_tensor() {
2976            Some(input) => self.reduce_min(input, axes),
2977            None => Err(crate::Error::unsupported(
2978                "reduce_min",
2979                "backend does not accept borrowed tensor views at this execution boundary",
2980            )),
2981        }
2982    }
2983}
2984
2985/// Dot-general operations.
2986///
2987/// # Examples
2988///
2989/// ```rust
2990/// use tenferro_tensor::TensorDot;
2991///
2992/// fn accepts_dot<B: TensorDot>(_backend: &mut B) {}
2993/// ```
2994pub trait TensorDot: TensorElementwise {
2995    /// # Errors
2996    ///
2997    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2998    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2999    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3000    /// backend execution or storage access cannot provide the requested result.
3001    fn dot_general(
3002        &mut self,
3003        lhs: &Tensor,
3004        rhs: &Tensor,
3005        config: &DotGeneralConfig,
3006    ) -> crate::Result<Tensor>;
3007
3008    #[doc(hidden)]
3009    fn dot_general_read(
3010        &mut self,
3011        lhs: TensorRead<'_>,
3012        rhs: TensorRead<'_>,
3013        config: &DotGeneralConfig,
3014    ) -> crate::Result<Tensor> {
3015        match (lhs.as_tensor(), rhs.as_tensor()) {
3016            (Some(lhs), Some(rhs)) => self.dot_general(lhs, rhs, config),
3017            _ => {
3018                let lhs = self.to_contiguous_read(lhs)?;
3019                let rhs = self.to_contiguous_read(rhs)?;
3020                self.dot_general(&lhs, &rhs, config)
3021            }
3022        }
3023    }
3024
3025    /// Overwrite caller-provided output with dot-general from read inputs.
3026    ///
3027    /// This is the dot/GEMM spelling of `_into`: the previous output value is
3028    /// not read. Use [`TensorDot::dot_general_read_into_accum`] for explicit
3029    /// read-modify-write accumulation.
3030    ///
3031    /// # Examples
3032    ///
3033    /// ```rust
3034    /// use tenferro_tensor::{DotGeneralConfig, TensorDot, TensorRead, TensorWrite};
3035    ///
3036    /// fn dot_into<B: TensorDot>(
3037    ///     backend: &mut B,
3038    ///     lhs: TensorRead<'_>,
3039    ///     rhs: TensorRead<'_>,
3040    ///     config: &DotGeneralConfig,
3041    ///     out: TensorWrite<'_>,
3042    /// ) -> tenferro_tensor::Result<()> {
3043    ///     backend.dot_general_read_into(lhs, rhs, config, out)
3044    /// }
3045    /// ```
3046    /// # Errors
3047    ///
3048    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3049    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3050    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3051    /// backend execution or storage access cannot provide the requested result.
3052    fn dot_general_read_into(
3053        &mut self,
3054        lhs: TensorRead<'_>,
3055        rhs: TensorRead<'_>,
3056        config: &DotGeneralConfig,
3057        out: TensorWrite<'_>,
3058    ) -> crate::Result<()> {
3059        let accumulation = DotGeneralAccumulation::overwrite(lhs.dtype())?;
3060        self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3061    }
3062
3063    #[doc(hidden)]
3064    fn dot_general_with_conj(
3065        &mut self,
3066        lhs: &Tensor,
3067        rhs: &Tensor,
3068        config: &DotGeneralConfig,
3069        lhs_conj: bool,
3070        rhs_conj: bool,
3071    ) -> crate::Result<Tensor> {
3072        if !lhs_conj && !rhs_conj {
3073            return self.dot_general(lhs, rhs, config);
3074        }
3075
3076        let lhs_tmp;
3077        let lhs_ref = if lhs_conj {
3078            lhs_tmp = self.conj(lhs)?;
3079            &lhs_tmp
3080        } else {
3081            lhs
3082        };
3083        let rhs_tmp;
3084        let rhs_ref = if rhs_conj {
3085            rhs_tmp = self.conj(rhs)?;
3086            &rhs_tmp
3087        } else {
3088            rhs
3089        };
3090        self.dot_general(lhs_ref, rhs_ref, config)
3091    }
3092
3093    #[allow(clippy::too_many_arguments)]
3094    #[doc(hidden)]
3095    fn dot_general_with_conj_read(
3096        &mut self,
3097        lhs: TensorRead<'_>,
3098        rhs: TensorRead<'_>,
3099        config: &DotGeneralConfig,
3100        lhs_conj: bool,
3101        rhs_conj: bool,
3102    ) -> crate::Result<Tensor> {
3103        if !lhs_conj && !rhs_conj {
3104            return self.dot_general_read(lhs, rhs, config);
3105        }
3106
3107        let lhs_tmp;
3108        let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
3109            tensor
3110        } else {
3111            lhs_tmp = self.to_contiguous_read(lhs)?;
3112            &lhs_tmp
3113        };
3114        let rhs_tmp;
3115        let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
3116            tensor
3117        } else {
3118            rhs_tmp = self.to_contiguous_read(rhs)?;
3119            &rhs_tmp
3120        };
3121        self.dot_general_with_conj(lhs_ref, rhs_ref, config, lhs_conj, rhs_conj)
3122    }
3123
3124    /// Apply scaled dot-general accumulation into caller-provided output.
3125    ///
3126    /// This is explicitly read-modify-write when `accumulation.beta` is nonzero:
3127    /// `out = alpha * dot_general(lhs, rhs) + beta * out`.
3128    ///
3129    /// # Examples
3130    ///
3131    /// ```rust
3132    /// use tenferro_tensor::{
3133    ///     DotGeneralAccumulation, DotGeneralConfig, TensorDot, TensorRead, TensorWrite,
3134    /// };
3135    ///
3136    /// fn dot_add_to<B: TensorDot>(
3137    ///     backend: &mut B,
3138    ///     lhs: TensorRead<'_>,
3139    ///     rhs: TensorRead<'_>,
3140    ///     config: &DotGeneralConfig,
3141    ///     out: TensorWrite<'_>,
3142    /// ) -> tenferro_tensor::Result<()> {
3143    ///     let accumulation = DotGeneralAccumulation::add_to(lhs.dtype())?;
3144    ///     backend.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3145    /// }
3146    /// ```
3147    /// # Errors
3148    ///
3149    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3150    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3151    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3152    /// backend execution or storage access cannot provide the requested result.
3153    fn dot_general_read_into_accum(
3154        &mut self,
3155        lhs: TensorRead<'_>,
3156        rhs: TensorRead<'_>,
3157        config: &DotGeneralConfig,
3158        accumulation: DotGeneralAccumulation,
3159        out: TensorWrite<'_>,
3160    ) -> crate::Result<()> {
3161        dot_general_accum_via_temp(self, lhs, rhs, config, accumulation, out)
3162    }
3163}
3164
3165/// Session-scoped cached dot-general operations.
3166///
3167/// # Examples
3168///
3169/// ```rust
3170/// use tenferro_tensor::BackendSession;
3171///
3172/// fn accepts_session_dot<S: BackendSession + ?Sized>(_session: &mut S) {}
3173/// ```
3174pub trait SessionCachedDot: TensorDot {
3175    #[doc(hidden)]
3176    fn dot_general_cached(
3177        &mut self,
3178        _cache_slot: Option<usize>,
3179        lhs: &Tensor,
3180        rhs: &Tensor,
3181        config: &DotGeneralConfig,
3182    ) -> crate::Result<Tensor> {
3183        self.dot_general(lhs, rhs, config)
3184    }
3185
3186    #[doc(hidden)]
3187    fn dot_general_read_cached(
3188        &mut self,
3189        cache_slot: Option<usize>,
3190        lhs: TensorRead<'_>,
3191        rhs: TensorRead<'_>,
3192        config: &DotGeneralConfig,
3193    ) -> crate::Result<Tensor> {
3194        match (lhs.as_tensor(), rhs.as_tensor()) {
3195            (Some(lhs), Some(rhs)) => self.dot_general_cached(cache_slot, lhs, rhs, config),
3196            _ => {
3197                let lhs = self.to_contiguous_read(lhs)?;
3198                let rhs = self.to_contiguous_read(rhs)?;
3199                self.dot_general_cached(cache_slot, &lhs, &rhs, config)
3200            }
3201        }
3202    }
3203
3204    // Mirrors the dot-general signature plus runtime-cache metadata.
3205    #[allow(clippy::too_many_arguments)]
3206    #[doc(hidden)]
3207    fn dot_general_with_conj_cached(
3208        &mut self,
3209        _cache_slot: Option<usize>,
3210        lhs: &Tensor,
3211        rhs: &Tensor,
3212        config: &DotGeneralConfig,
3213        lhs_conj: bool,
3214        rhs_conj: bool,
3215    ) -> crate::Result<Tensor> {
3216        self.dot_general_with_conj(lhs, rhs, config, lhs_conj, rhs_conj)
3217    }
3218
3219    // Mirrors the dot-general read signature plus runtime-cache metadata.
3220    #[allow(clippy::too_many_arguments)]
3221    #[doc(hidden)]
3222    fn dot_general_with_conj_read_cached(
3223        &mut self,
3224        cache_slot: Option<usize>,
3225        lhs: TensorRead<'_>,
3226        rhs: TensorRead<'_>,
3227        config: &DotGeneralConfig,
3228        lhs_conj: bool,
3229        rhs_conj: bool,
3230    ) -> crate::Result<Tensor> {
3231        if !lhs_conj && !rhs_conj {
3232            return self.dot_general_read_cached(cache_slot, lhs, rhs, config);
3233        }
3234
3235        let lhs_tmp;
3236        let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
3237            tensor
3238        } else {
3239            lhs_tmp = self.to_contiguous_read(lhs)?;
3240            &lhs_tmp
3241        };
3242        let rhs_tmp;
3243        let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
3244            tensor
3245        } else {
3246            rhs_tmp = self.to_contiguous_read(rhs)?;
3247            &rhs_tmp
3248        };
3249        self.dot_general_with_conj_cached(cache_slot, lhs_ref, rhs_ref, config, lhs_conj, rhs_conj)
3250    }
3251
3252    /// Apply session-cached scaled dot-general accumulation into output.
3253    ///
3254    /// The cache slot is session-local metadata; `accumulation` still controls
3255    /// overwrite versus read-modify-write semantics.
3256    ///
3257    /// # Examples
3258    ///
3259    /// ```rust
3260    /// use tenferro_tensor::{
3261    ///     DotGeneralAccumulation, DotGeneralConfig, SessionCachedDot, TensorRead, TensorWrite,
3262    /// };
3263    ///
3264    /// fn session_cached_dot_add_to<S: SessionCachedDot + ?Sized>(
3265    ///     session: &mut S,
3266    ///     lhs: TensorRead<'_>,
3267    ///     rhs: TensorRead<'_>,
3268    ///     config: &DotGeneralConfig,
3269    ///     out: TensorWrite<'_>,
3270    /// ) -> tenferro_tensor::Result<()> {
3271    ///     let accumulation = DotGeneralAccumulation::add_to(lhs.dtype())?;
3272    ///     session.dot_general_read_into_accum_cached(
3273    ///         Some(0),
3274    ///         lhs,
3275    ///         rhs,
3276    ///         config,
3277    ///         accumulation,
3278    ///         out,
3279    ///     )
3280    /// }
3281    /// ```
3282    /// # Errors
3283    ///
3284    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3285    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3286    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3287    /// backend execution or storage access cannot provide the requested result.
3288    fn dot_general_read_into_accum_cached(
3289        &mut self,
3290        _cache_slot: Option<usize>,
3291        lhs: TensorRead<'_>,
3292        rhs: TensorRead<'_>,
3293        config: &DotGeneralConfig,
3294        accumulation: DotGeneralAccumulation,
3295        out: TensorWrite<'_>,
3296    ) -> crate::Result<()> {
3297        self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3298    }
3299
3300    #[doc(hidden)]
3301    fn grouped_gemm_cached(
3302        &mut self,
3303        _cache_slot: Option<usize>,
3304        lhs: TensorRead<'_>,
3305        rhs: TensorRead<'_>,
3306        config: &GroupedGemmConfig<'_>,
3307        out: TensorWrite<'_>,
3308    ) -> crate::Result<()> {
3309        grouped_gemm_default(self, lhs, rhs, config, out)
3310    }
3311}
3312
3313/// Indexing, slicing, and padding operations.
3314///
3315/// # Examples
3316///
3317/// ```rust
3318/// use tenferro_tensor::TensorIndexing;
3319///
3320/// fn accepts_indexing<B: TensorIndexing>(_backend: &mut B) {}
3321/// ```
3322pub trait TensorIndexing {
3323    /// # Errors
3324    ///
3325    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3326    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3327    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3328    /// backend execution or storage access cannot provide the requested result.
3329    fn gather(
3330        &mut self,
3331        operand: &Tensor,
3332        start_indices: &Tensor,
3333        config: &GatherConfig,
3334    ) -> crate::Result<Tensor>;
3335    /// # Errors
3336    ///
3337    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3338    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3339    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3340    /// backend execution or storage access cannot provide the requested result.
3341    fn scatter(
3342        &mut self,
3343        operand: &Tensor,
3344        scatter_indices: &Tensor,
3345        updates: &Tensor,
3346        config: &ScatterConfig,
3347    ) -> crate::Result<Tensor>;
3348    /// # Errors
3349    ///
3350    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3351    /// for invalid shapes, ranks, axes, dtypes, or output metadata. In
3352    /// particular, a limit greater than the corresponding input dimension is
3353    /// reported as [`crate::ValidationError::InvalidArgument`] with the
3354    /// `"configuration"` argument. It returns [`crate::Error::BackendFailure`]
3355    /// or [`crate::Error::BackendSource`] when backend execution or storage
3356    /// access cannot provide the requested result.
3357    fn slice(&mut self, input: &Tensor, config: &SliceConfig) -> crate::Result<Tensor>;
3358    /// # Errors
3359    ///
3360    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3361    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3362    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3363    /// backend execution or storage access cannot provide the requested result.
3364    fn dynamic_slice(
3365        &mut self,
3366        input: &Tensor,
3367        starts: &Tensor,
3368        slice_sizes: &[usize],
3369    ) -> crate::Result<Tensor>;
3370    /// # Errors
3371    ///
3372    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3373    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3374    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3375    /// backend execution or storage access cannot provide the requested result.
3376    fn dynamic_update_slice(
3377        &mut self,
3378        operand: &Tensor,
3379        update: &Tensor,
3380        starts: &Tensor,
3381    ) -> crate::Result<Tensor>;
3382    /// # Errors
3383    ///
3384    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3385    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3386    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3387    /// backend execution or storage access cannot provide the requested result.
3388    fn pad(&mut self, input: &Tensor, config: &PadConfig) -> crate::Result<Tensor>;
3389    /// # Errors
3390    ///
3391    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3392    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3393    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3394    /// backend execution or storage access cannot provide the requested result.
3395    fn concatenate(&mut self, inputs: &[&Tensor], axis: usize) -> crate::Result<Tensor>;
3396    /// # Errors
3397    ///
3398    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3399    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3400    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3401    /// backend execution or storage access cannot provide the requested result.
3402    fn reverse(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
3403}
3404
3405/// Backend-owned canonicalization for typed tensor views.
3406///
3407/// Implementations must preserve the input placement family. CPU backends
3408/// canonicalize host views through explicit host copies and reject backend
3409/// buffers with a diagnostic that asks the caller to download first. GPU
3410/// backends canonicalize GPU-resident views on the same device and reject host
3411/// buffers with an upload hint.
3412///
3413/// [`TensorViewCanonicalization::copy_into`] requires source and destination
3414/// shapes, scalar dtypes, and placement families to match. The destination
3415/// view must be internally non-overlapping, and source and destination backing
3416/// allocations must not alias unless an implementation explicitly documents
3417/// and supports that case. Implementations may reject layouts their native
3418/// kernels cannot consume.
3419///
3420/// CUDA currently accepts only a compact column-major source view with offset
3421/// zero that covers its full allocation; arbitrary-stride destinations remain
3422/// supported. Canonicalization and copying are same-placement operations: they
3423/// must not perform hidden host/device transfers or silently materialize an
3424/// unsupported source layout.
3425///
3426/// This trait is intentionally separate from [`BackendSession`] so generic
3427/// typed methods do not change the object-safety contract of `dyn BackendSession`.
3428///
3429/// # Examples
3430///
3431/// ```rust
3432/// use tenferro_tensor::{DynRank, TensorViewCanonicalization, TypedTensor};
3433///
3434/// fn compact_i32<B: TensorViewCanonicalization<i32, DynRank>>(
3435///     backend: &mut B,
3436///     tensor: &TypedTensor<i32>,
3437/// ) -> tenferro_tensor::Result<TypedTensor<i32>> {
3438///     backend.to_contiguous(&tensor.as_view())
3439/// }
3440///
3441/// fn copy_i32<B: TensorViewCanonicalization<i32, DynRank>>(
3442///     backend: &mut B,
3443///     src: &TypedTensor<i32>,
3444///     dst: &mut TypedTensor<i32>,
3445/// ) -> tenferro_tensor::Result<()> {
3446///     backend.copy_into(&src.as_view(), &mut dst.as_view_mut())
3447/// }
3448/// ```
3449pub trait TensorViewCanonicalization<T: TensorScalar, R: TensorRank> {
3450    /// # Errors
3451    ///
3452    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3453    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3454    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3455    /// backend execution or storage access cannot provide the requested result.
3456    fn to_contiguous(
3457        &mut self,
3458        view: &TypedTensorView<'_, T, R>,
3459    ) -> crate::Result<TypedTensor<T, R>>;
3460
3461    /// # Errors
3462    ///
3463    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3464    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3465    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3466    /// backend execution or storage access cannot provide the requested result.
3467    fn copy_into(
3468        &mut self,
3469        src: &TypedTensorView<'_, T, R>,
3470        dst: &mut TypedTensorViewMut<'_, T, R>,
3471    ) -> crate::Result<()>;
3472}
3473
3474/// Optional elementwise fusion execution.
3475///
3476/// # Examples
3477///
3478/// ```rust
3479/// use tenferro_tensor::TensorFusion;
3480///
3481/// fn accepts_fusion<B: TensorFusion>(_backend: &mut B) {}
3482/// ```
3483pub trait TensorFusion {
3484    #[doc(hidden)]
3485    fn execute_elementwise_fusion(
3486        &mut self,
3487        _inputs: &[&Tensor],
3488        _plan: &ElementwiseFusionPlan,
3489    ) -> crate::Result<Option<Vec<Tensor>>> {
3490        Ok(None)
3491    }
3492
3493    #[doc(hidden)]
3494    #[allow(clippy::too_many_arguments)]
3495    fn execute_broadcast_multiply(
3496        &mut self,
3497        _lhs: TensorRead<'_>,
3498        _lhs_shape: &[usize],
3499        _lhs_dims: &[usize],
3500        _rhs: TensorRead<'_>,
3501        _rhs_shape: &[usize],
3502        _rhs_dims: &[usize],
3503    ) -> crate::Result<Option<Tensor>> {
3504        Ok(None)
3505    }
3506
3507    #[doc(hidden)]
3508    #[allow(clippy::too_many_arguments)]
3509    fn execute_broadcast_multiply_value(
3510        &mut self,
3511        lhs: TensorRead<'_>,
3512        lhs_shape: &[usize],
3513        lhs_dims: &[usize],
3514        rhs: TensorRead<'_>,
3515        rhs_shape: &[usize],
3516        rhs_dims: &[usize],
3517    ) -> crate::Result<Option<TensorValue>> {
3518        self.execute_broadcast_multiply(lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims)
3519            .map(|tensor| tensor.map(TensorValue::from_tensor))
3520    }
3521}
3522
3523/// Backend buffer lifecycle operations.
3524///
3525/// # Examples
3526///
3527/// ```rust
3528/// use tenferro_tensor::TensorBuffer;
3529///
3530/// fn accepts_buffer<B: TensorBuffer>(_backend: &mut B) {}
3531/// ```
3532pub trait TensorBuffer {
3533    fn reclaim_buffer(&mut self, _tensor: Tensor) {}
3534}
3535
3536/// Device transfer operations on backend boundaries.
3537///
3538/// # Examples
3539///
3540/// ```rust
3541/// use tenferro_tensor::TensorDeviceTransfer;
3542///
3543/// fn accepts_transfer<B: TensorDeviceTransfer>(_backend: &mut B) {}
3544/// ```
3545pub trait TensorDeviceTransfer {
3546    /// Explicitly copy a provider-owned read target into host storage.
3547    ///
3548    /// Implementations must not return the input unchanged or stage through an
3549    /// unrelated provider. A backend that cannot transfer the requested read
3550    /// target returns a typed unsupported error.
3551    ///
3552    /// # Errors
3553    ///
3554    /// Returns [`crate::Error::Unsupported`] when the implementation cannot
3555    /// perform the requested transfer, or a typed validation/backend error when
3556    /// the source cannot be read.
3557    fn download_to_host(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor>;
3558
3559    /// Explicitly copy a host read target into provider storage.
3560    ///
3561    /// # Errors
3562    ///
3563    /// Returns [`crate::Error::Unsupported`] when the implementation cannot
3564    /// perform the requested transfer, or a typed validation/backend error when
3565    /// the source cannot be read.
3566    fn upload_host_tensor(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor>;
3567}
3568
3569/// Runtime cache associated with a backend.
3570///
3571/// # Examples
3572///
3573/// ```rust
3574/// use tenferro_tensor::BackendRuntimeCache;
3575///
3576/// fn accepts_runtime_cache<B: BackendRuntimeCache>(_backend: &B) {}
3577/// ```
3578pub trait BackendRuntimeCache {
3579    #[doc(hidden)]
3580    type RuntimeCache: RuntimeCacheControl + Send + Sync + 'static;
3581}
3582
3583/// Backend-owned cached dot-general operations.
3584///
3585/// # Examples
3586///
3587/// ```rust
3588/// use tenferro_tensor::BackendCachedDot;
3589///
3590/// fn accepts_backend_cached_dot<B: BackendCachedDot>(_backend: &mut B) {}
3591/// ```
3592pub trait BackendCachedDot: BackendRuntimeCache + TensorDot {
3593    #[doc(hidden)]
3594    fn dot_general_cached(
3595        &mut self,
3596        _cache: &mut Self::RuntimeCache,
3597        _cache_slot: Option<usize>,
3598        lhs: &Tensor,
3599        rhs: &Tensor,
3600        config: &DotGeneralConfig,
3601    ) -> crate::Result<Tensor> {
3602        self.dot_general(lhs, rhs, config)
3603    }
3604
3605    #[doc(hidden)]
3606    fn dot_general_read_cached(
3607        &mut self,
3608        cache: &mut Self::RuntimeCache,
3609        cache_slot: Option<usize>,
3610        lhs: TensorRead<'_>,
3611        rhs: TensorRead<'_>,
3612        config: &DotGeneralConfig,
3613    ) -> crate::Result<Tensor> {
3614        match (lhs.as_tensor(), rhs.as_tensor()) {
3615            (Some(lhs), Some(rhs)) => self.dot_general_cached(cache, cache_slot, lhs, rhs, config),
3616            _ => {
3617                let lhs = self.to_contiguous_read(lhs)?;
3618                let rhs = self.to_contiguous_read(rhs)?;
3619                self.dot_general_cached(cache, cache_slot, &lhs, &rhs, config)
3620            }
3621        }
3622    }
3623
3624    // Mirrors the dot-general signature plus runtime-cache metadata.
3625    #[allow(clippy::too_many_arguments)]
3626    #[doc(hidden)]
3627    fn dot_general_with_conj_cached(
3628        &mut self,
3629        _cache: &mut Self::RuntimeCache,
3630        _cache_slot: Option<usize>,
3631        lhs: &Tensor,
3632        rhs: &Tensor,
3633        config: &DotGeneralConfig,
3634        lhs_conj: bool,
3635        rhs_conj: bool,
3636    ) -> crate::Result<Tensor> {
3637        self.dot_general_with_conj(lhs, rhs, config, lhs_conj, rhs_conj)
3638    }
3639
3640    // Mirrors the dot-general read signature plus runtime-cache metadata.
3641    #[allow(clippy::too_many_arguments)]
3642    #[doc(hidden)]
3643    fn dot_general_with_conj_read_cached(
3644        &mut self,
3645        cache: &mut Self::RuntimeCache,
3646        cache_slot: Option<usize>,
3647        lhs: TensorRead<'_>,
3648        rhs: TensorRead<'_>,
3649        config: &DotGeneralConfig,
3650        lhs_conj: bool,
3651        rhs_conj: bool,
3652    ) -> crate::Result<Tensor> {
3653        if !lhs_conj && !rhs_conj {
3654            return self.dot_general_read_cached(cache, cache_slot, lhs, rhs, config);
3655        }
3656
3657        let lhs_tmp;
3658        let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
3659            tensor
3660        } else {
3661            lhs_tmp = self.to_contiguous_read(lhs)?;
3662            &lhs_tmp
3663        };
3664        let rhs_tmp;
3665        let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
3666            tensor
3667        } else {
3668            rhs_tmp = self.to_contiguous_read(rhs)?;
3669            &rhs_tmp
3670        };
3671        self.dot_general_with_conj_cached(
3672            cache, cache_slot, lhs_ref, rhs_ref, config, lhs_conj, rhs_conj,
3673        )
3674    }
3675
3676    /// Apply cached scaled dot-general accumulation into caller-provided output.
3677    ///
3678    /// The cache slot identifies backend-local analysis metadata only; output
3679    /// semantics are still fully described by `accumulation`.
3680    ///
3681    /// # Examples
3682    ///
3683    /// ```rust
3684    /// use tenferro_tensor::{
3685    ///     BackendCachedDot, BackendRuntimeCache, DotGeneralAccumulation, DotGeneralConfig,
3686    ///     TensorRead, TensorWrite,
3687    /// };
3688    ///
3689    /// fn cached_dot_add_to<B: BackendCachedDot>(
3690    ///     backend: &mut B,
3691    ///     cache: &mut B::RuntimeCache,
3692    ///     lhs: TensorRead<'_>,
3693    ///     rhs: TensorRead<'_>,
3694    ///     config: &DotGeneralConfig,
3695    ///     out: TensorWrite<'_>,
3696    /// ) -> tenferro_tensor::Result<()>
3697    /// where
3698    ///     B: BackendRuntimeCache,
3699    /// {
3700    ///     let accumulation = DotGeneralAccumulation::add_to(lhs.dtype())?;
3701    ///     backend.dot_general_read_into_accum_cached(
3702    ///         cache,
3703    ///         Some(0),
3704    ///         lhs,
3705    ///         rhs,
3706    ///         config,
3707    ///         accumulation,
3708    ///         out,
3709    ///     )
3710    /// }
3711    /// ```
3712    #[allow(clippy::too_many_arguments)]
3713    /// # Errors
3714    ///
3715    /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3716    /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3717    /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3718    /// backend execution or storage access cannot provide the requested result.
3719    fn dot_general_read_into_accum_cached(
3720        &mut self,
3721        _cache: &mut Self::RuntimeCache,
3722        _cache_slot: Option<usize>,
3723        lhs: TensorRead<'_>,
3724        rhs: TensorRead<'_>,
3725        config: &DotGeneralConfig,
3726        accumulation: DotGeneralAccumulation,
3727        out: TensorWrite<'_>,
3728    ) -> crate::Result<()> {
3729        self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3730    }
3731
3732    #[doc(hidden)]
3733    fn grouped_gemm_cached(
3734        &mut self,
3735        _cache: &mut Self::RuntimeCache,
3736        _cache_slot: Option<usize>,
3737        lhs: TensorRead<'_>,
3738        rhs: TensorRead<'_>,
3739        config: &GroupedGemmConfig<'_>,
3740        out: TensorWrite<'_>,
3741    ) -> crate::Result<()> {
3742        grouped_gemm_default(self, lhs, rhs, config, out)
3743    }
3744}
3745
3746/// Backend execution-session entry points.
3747///
3748/// `with_backend_session` is the canonical user entry; one-shot concrete ops
3749/// delegate to the session form. `with_backend_session_cached` is the
3750/// runtime-cache-aware entry used by the runtime layer.
3751///
3752/// # Examples
3753///
3754/// ```rust
3755/// use tenferro_tensor::BackendSessionHost;
3756///
3757/// fn accepts_session_host<B: BackendSessionHost>(_backend: &mut B) {}
3758/// ```
3759pub trait BackendSessionHost: BackendRuntimeCache {
3760    fn with_backend_session<R: Send>(
3761        &mut self,
3762        f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
3763    ) -> R
3764    where
3765        Self: TensorBackend + Sized,
3766    {
3767        default_backend_session(self, f)
3768    }
3769
3770    #[doc(hidden)]
3771    fn with_backend_session_cached<R: Send>(
3772        &mut self,
3773        _cache: &mut Self::RuntimeCache,
3774        f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
3775    ) -> R
3776    where
3777        Self: TensorBackend + Sized,
3778    {
3779        self.with_backend_session(f)
3780    }
3781}
3782
3783/// Operation capabilities shared by backends and backend sessions.
3784#[doc(hidden)]
3785pub trait TensorBackendOps:
3786    TensorElementwise
3787    + TensorAnalytic
3788    + TensorStructural
3789    + TensorReduction
3790    + TensorIndexing
3791    + TensorDot
3792    + TensorFusion
3793    + TensorBuffer
3794{
3795}
3796
3797impl<T> TensorBackendOps for T where
3798    T: TensorElementwise
3799        + TensorAnalytic
3800        + TensorStructural
3801        + TensorReduction
3802        + TensorIndexing
3803        + TensorDot
3804        + TensorFusion
3805        + TensorBuffer
3806        + ?Sized
3807{
3808}
3809
3810/// Validate the shared input contract for [`BackendSession::vdot_read`].
3811#[doc(hidden)]
3812pub fn validate_vdot_read(lhs: &TensorRead<'_>, rhs: &TensorRead<'_>) -> crate::Result<()> {
3813    let op = "BackendSession::vdot_read";
3814    validate_supported_blas1_dtype(op, lhs.dtype())?;
3815    if rhs.dtype() != lhs.dtype() {
3816        return Err(Error::dtype_mismatch(op, lhs.dtype(), rhs.dtype()));
3817    }
3818    if lhs.shape() != rhs.shape() {
3819        return Err(Error::shape_mismatch(
3820            op,
3821            lhs.shape().to_vec(),
3822            rhs.shape().to_vec(),
3823        ));
3824    }
3825    validate_shape_product(op, lhs.shape())?;
3826    validate_compatible_placement(op, lhs, rhs)
3827}
3828
3829/// Validate the shared input contract for [`BackendSession::norm_squared_read`].
3830#[doc(hidden)]
3831pub fn validate_norm_squared_read(input: &TensorRead<'_>) -> crate::Result<()> {
3832    validate_supported_blas1_dtype("BackendSession::norm_squared_read", input.dtype())?;
3833    validate_shape_product("BackendSession::norm_squared_read", input.shape()).map(|_| ())
3834}
3835
3836/// Validate the shared input and destination contract for
3837/// [`BackendSession::axpby_read_into_accum`].
3838#[doc(hidden)]
3839pub fn validate_axpby_read_into_accum(
3840    alpha: ContractionScalar,
3841    x: &TensorRead<'_>,
3842    beta: ContractionScalar,
3843    y: &TensorWrite<'_>,
3844) -> crate::Result<()> {
3845    let op = "BackendSession::axpby_read_into_accum";
3846    validate_supported_blas1_dtype(op, x.dtype())?;
3847    if y.dtype() != x.dtype() {
3848        return Err(Error::dtype_mismatch(op, x.dtype(), y.dtype()));
3849    }
3850    if alpha.dtype() != x.dtype() {
3851        return Err(Error::dtype_mismatch(op, x.dtype(), alpha.dtype()));
3852    }
3853    if beta.dtype() != x.dtype() {
3854        return Err(Error::dtype_mismatch(op, x.dtype(), beta.dtype()));
3855    }
3856    if x.shape() != y.shape() {
3857        return Err(Error::shape_mismatch(
3858            op,
3859            x.shape().to_vec(),
3860            y.shape().to_vec(),
3861        ));
3862    }
3863    validate_shape_product(op, x.shape())?;
3864    if !y.as_read().is_col_major_contiguous()? {
3865        return Err(Error::invalid_argument(
3866            op,
3867            "y",
3868            "destination must be compact column-major and injective",
3869        ));
3870    }
3871    validate_compatible_placement(op, x, &y.as_read())?;
3872    validate_read_into_destination(op, std::slice::from_ref(x), y)
3873}
3874
3875/// # Errors
3876///
3877/// Returns [`Error::Unsupported`] when `dtype` is not floating or complex.
3878fn validate_supported_blas1_dtype(op: &'static str, dtype: DType) -> crate::Result<()> {
3879    if matches!(dtype, DType::F32 | DType::F64 | DType::C32 | DType::C64) {
3880        Ok(())
3881    } else {
3882        Err(Error::unsupported(
3883            op,
3884            format!("unsupported dtype {dtype:?}; supported dtypes: F32/F64/C32/C64"),
3885        ))
3886    }
3887}
3888
3889/// # Errors
3890///
3891/// Returns [`Error::Validation`] with [`ValidationError::IntegerOverflow`] when
3892/// the checked shape product overflows.
3893fn validate_shape_product(op: &'static str, shape: &[usize]) -> crate::Result<usize> {
3894    crate::validate::checked_shape_product(op, "shape", shape)
3895}
3896
3897/// # Errors
3898///
3899/// Returns [`Error::Validation`] with [`ValidationError::InvalidArgument`] when
3900/// placements differ.
3901fn validate_compatible_placement(
3902    op: &'static str,
3903    lhs: &TensorRead<'_>,
3904    rhs: &TensorRead<'_>,
3905) -> crate::Result<()> {
3906    if lhs.placement() != rhs.placement() {
3907        return Err(Error::invalid_argument(
3908            op,
3909            "placement",
3910            "tensor inputs must have compatible placement",
3911        ));
3912    }
3913    Ok(())
3914}
3915
3916/// Execution session surface for dense tensor backends.
3917///
3918/// All operations run within a backend-owned execution scope such as a CPU
3919/// thread policy or a GPU stream. Individual ops must not try to re-enter that
3920/// scope.
3921///
3922/// # Examples
3923///
3924/// ```rust
3925/// use tenferro_tensor::{BackendSessionHost, Tensor, TypedTensor};
3926///
3927/// fn add_in_session<B: BackendSessionHost>(
3928///     backend: &mut B,
3929///     a: &Tensor,
3930///     b: &Tensor,
3931/// ) -> tenferro_tensor::Result<Tensor>
3932/// where
3933///     B: tenferro_tensor::TensorBackend,
3934/// {
3935///     backend.with_backend_session(|exec| exec.add(a, b))
3936/// }
3937/// ```
3938pub trait BackendSession: TensorBackendOps + SessionCachedDot + TensorDeviceTransfer {
3939    /// Build-local identity for backend-extension session capability dispatch.
3940    #[doc(hidden)]
3941    fn session_type_id(&self) -> TypeId;
3942
3943    /// Compute the all-axis conjugating dot product without transferring either input.
3944    ///
3945    /// The result is a rank-0 tensor with the input dtype and has the value
3946    /// `sum(conj(lhs) * rhs)`. Borrowed views remain borrowed through the
3947    /// backend's existing same-placement planning boundary.
3948    ///
3949    /// # Examples
3950    ///
3951    /// ```rust
3952    /// use tenferro_tensor::{BackendSession, TensorRead};
3953    ///
3954    /// fn vdot(session: &mut dyn BackendSession, x: TensorRead<'_>, y: TensorRead<'_>)
3955    ///     -> tenferro_tensor::Result<tenferro_tensor::Tensor>
3956    /// {
3957    ///     session.vdot_read(x, y)
3958    /// }
3959    /// ```
3960    ///
3961    /// # Errors
3962    ///
3963    /// Returns [`Error::Unsupported`] when the backend does not override this
3964    /// capability or the dtype is unsupported; [`Error::Validation`] with
3965    /// `DTypeMismatch`, `ShapeMismatch`, or `InvalidArgument` when dtype, shape,
3966    /// or placement differs; [`Error::RuntimeState`] for inaccessible backend
3967    /// storage; or [`Error::BackendSource`] when provider execution fails.
3968    fn vdot_read(&mut self, _lhs: TensorRead<'_>, _rhs: TensorRead<'_>) -> crate::Result<Tensor> {
3969        Err(Error::unsupported(
3970            "BackendSession::vdot_read",
3971            "backend session does not implement vdot_read",
3972        ))
3973    }
3974
3975    /// Compute the all-axis sum of squared magnitudes without taking a square root.
3976    ///
3977    /// The result is rank 0 and is F32 for F32/C32 input or F64 for F64/C64
3978    /// input. No transfer or full-size algebra temporary is implied by this
3979    /// session contract.
3980    ///
3981    /// # Examples
3982    ///
3983    /// ```rust
3984    /// use tenferro_tensor::{BackendSession, TensorRead};
3985    ///
3986    /// fn norm_squared(session: &mut dyn BackendSession, x: TensorRead<'_>)
3987    ///     -> tenferro_tensor::Result<tenferro_tensor::Tensor>
3988    /// {
3989    ///     session.norm_squared_read(x)
3990    /// }
3991    /// ```
3992    ///
3993    /// # Errors
3994    ///
3995    /// Returns [`Error::Unsupported`] when the backend does not override this
3996    /// capability or the dtype is unsupported, [`Error::RuntimeState`] when
3997    /// backend storage is not host-accessible, or [`Error::BackendSource`] when
3998    /// reduction execution fails.
3999    fn norm_squared_read(&mut self, _input: TensorRead<'_>) -> crate::Result<Tensor> {
4000        Err(Error::unsupported(
4001            "BackendSession::norm_squared_read",
4002            "backend session does not implement norm_squared_read",
4003        ))
4004    }
4005
4006    /// Apply `y <- alpha * x + beta * y` in one pass into caller-owned storage.
4007    ///
4008    /// Scalars must have the exact tensor dtype; real coefficients for complex
4009    /// vectors are represented as complex values with zero imaginary part. The
4010    /// destination must be compact and injective, and any x/y storage overlap
4011    /// is rejected before mutation.
4012    ///
4013    /// # Examples
4014    ///
4015    /// ```rust
4016    /// use tenferro_tensor::{BackendSession, ContractionScalar, TensorRead, TensorWrite};
4017    ///
4018    /// fn axpby(session: &mut dyn BackendSession, x: TensorRead<'_>, y: TensorWrite<'_>)
4019    ///     -> tenferro_tensor::Result<()>
4020    /// {
4021    ///     session.axpby_read_into_accum(
4022    ///         ContractionScalar::F64(1.0), x, ContractionScalar::F64(0.0), y,
4023    ///     )
4024    /// }
4025    /// ```
4026    ///
4027    /// # Errors
4028    ///
4029    /// Returns [`Error::Unsupported`] when the backend does not override this
4030    /// capability or the dtype is unsupported; [`Error::Validation`] with
4031    /// `DTypeMismatch`, `ShapeMismatch`, or `InvalidArgument` for scalar/dtype,
4032    /// shape, placement, compactness, injectivity, or overlap failures; or
4033    /// [`Error::RuntimeState`] for inaccessible backend storage. Invalid
4034    /// requests leave the destination unchanged.
4035    fn axpby_read_into_accum(
4036        &mut self,
4037        _alpha: ContractionScalar,
4038        _x: TensorRead<'_>,
4039        _beta: ContractionScalar,
4040        _y: TensorWrite<'_>,
4041    ) -> crate::Result<()> {
4042        Err(Error::unsupported(
4043            "BackendSession::axpby_read_into_accum",
4044            "backend session does not implement axpby_read_into_accum",
4045        ))
4046    }
4047
4048    /// Erased pointer used only by backend leaf crates for a checked session
4049    /// capability bridge. The pointer is borrowed for the lifetime of `self`.
4050    ///
4051    /// # Safety
4052    ///
4053    /// The implementation must return a pointer to the same value represented
4054    /// by `self`, and that pointer must remain valid and uniquely borrowed for
4055    /// the duration of the `&mut self` borrow. Backend leaf crates may use this
4056    /// contract to recover a concrete session capability after checking
4057    /// [`Self::session_type_id`].
4058    #[doc(hidden)]
4059    unsafe fn session_data_mut(&mut self) -> *mut ();
4060}
4061
4062/// Standard runtime backend over dynamic [`Tensor`] values.
4063///
4064/// # Examples
4065///
4066/// ```rust
4067/// use tenferro_tensor::TensorBackend;
4068///
4069/// fn accepts_backend<B: TensorBackend>(_backend: &mut B) {}
4070/// ```
4071pub trait TensorBackend:
4072    BackendRuntimeCache
4073    + BackendSession
4074    + TensorBackendOps
4075    + BackendCachedDot
4076    + TensorDeviceTransfer
4077    + BackendSessionHost
4078{
4079}
4080
4081impl<T> SessionCachedDot for T where T: TensorBackend + ?Sized {}
4082
4083thread_local! {
4084    /// Tracks whether a session-entry closure is currently running on this
4085    /// thread, so nested session entry is caught in debug builds even for
4086    /// backends that do not override
4087    /// [`BackendSessionHost::with_backend_session`].
4088    static IN_SESSION: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
4089}
4090
4091/// Sets the in-session flag for the duration of a session closure and restores
4092/// it on exit, including on panic.
4093struct InSessionGuard;
4094
4095impl InSessionGuard {
4096    fn enter() -> Self {
4097        debug_assert!(
4098            !IN_SESSION.get(),
4099            "nested backend session entry: a session closure called \
4100             with_backend_session / default_backend_session again on this thread"
4101        );
4102        IN_SESSION.set(true);
4103        InSessionGuard
4104    }
4105}
4106
4107impl Drop for InSessionGuard {
4108    fn drop(&mut self) {
4109        IN_SESSION.set(false);
4110    }
4111}
4112
4113/// Run `f` with the thread-local in-session flag set, restoring it on exit
4114/// including on panic, and asserting (in debug builds) that the caller is not
4115/// already inside a session closure.
4116///
4117/// This is the portable nested-entry guard shared by every backend-session
4118/// entry point: [`default_backend_session`] and the GPU
4119/// `BackendSessionHost::with_backend_session` overrides. CPU keeps its own
4120/// release-mode `EXECUTION_OWNER` panic on top of this debug check.
4121#[doc(hidden)]
4122pub fn with_session_entry_guard<R>(f: impl FnOnce() -> R) -> R {
4123    let _guard = InSessionGuard::enter();
4124    f()
4125}
4126
4127/// Run a closure using the backend itself as a default execution session.
4128///
4129/// This is suitable for backends whose individual ops already manage their own
4130/// execution context.
4131///
4132/// # Examples
4133///
4134/// ```rust
4135/// use tenferro_tensor::{default_backend_session, TensorBackend};
4136///
4137/// fn run_with_default_session<B: TensorBackend>(backend: &mut B) -> usize {
4138///     default_backend_session(backend, |_exec| 1usize)
4139/// }
4140/// ```
4141pub fn default_backend_session<B: TensorBackend, R: Send>(
4142    backend: &mut B,
4143    f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
4144) -> R {
4145    with_session_entry_guard(|| f(backend))
4146}