Skip to main content

tenferro_tensor/
backend.rs

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