Skip to main content

tenferro_tensor/
backend.rs

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