Skip to main content

tenferro_runtime/runtime/
specialization.rs

1use tenferro_tensor::{DType, MemoryKind, Placement, StrideVec};
2
3use super::{
4    InputSignature, InputSpecializationRequirementsError, PrepareError, RankRequirement,
5    SpecializationError, StorageClass,
6};
7
8/// Selects how much placement metadata enters a specialization key.
9///
10/// # Examples
11///
12/// ```
13/// use tenferro_runtime::PlacementSpecialization;
14///
15/// assert_eq!(PlacementSpecialization::None, PlacementSpecialization::None);
16/// ```
17#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
18pub enum PlacementSpecialization {
19    /// Do not specialize on placement.
20    #[default]
21    None,
22    /// Specialize only on the storage class derived from memory kind.
23    StorageClass,
24    /// Specialize on full device placement metadata.
25    Device,
26}
27
28/// Selects how much layout metadata enters a specialization key.
29///
30/// # Examples
31///
32/// ```
33/// use tenferro_runtime::LayoutSpecialization;
34///
35/// assert_eq!(LayoutSpecialization::Class, LayoutSpecialization::Class);
36/// ```
37#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
38pub enum LayoutSpecialization {
39    /// Do not specialize on layout.
40    #[default]
41    None,
42    /// Specialize on layout class.
43    Class,
44    /// Specialize on exact strides.
45    ExactStrides,
46}
47
48/// Per-input specialization requirements.
49///
50/// # Examples
51///
52/// ```
53/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
54/// use tenferro_runtime::InputSpecializationRequirements;
55///
56/// let requirements = InputSpecializationRequirements::builder().build()?;
57/// assert!(!requirements.specializes_dtype());
58/// # Ok(())
59/// # }
60/// ```
61#[derive(Clone, Debug, Eq, Hash, PartialEq)]
62pub struct InputSpecializationRequirements {
63    dtype: bool,
64    rank: bool,
65    concrete_dimensions: Vec<u32>,
66    placement: PlacementSpecialization,
67    layout: LayoutSpecialization,
68    alignment_log2: Option<u8>,
69}
70
71impl InputSpecializationRequirements {
72    /// Return a new requirements builder.
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
78    /// use tenferro_runtime::InputSpecializationRequirements;
79    ///
80    /// let mut builder = InputSpecializationRequirements::builder();
81    /// builder.dtype(true);
82    /// assert!(builder.build()?.specializes_dtype());
83    /// # Ok(())
84    /// # }
85    /// ```
86    pub fn builder() -> InputSpecializationRequirementsBuilder {
87        InputSpecializationRequirementsBuilder::new()
88    }
89
90    /// Return whether dtype is specialized.
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
96    /// use tenferro_runtime::InputSpecializationRequirements;
97    ///
98    /// let mut builder = InputSpecializationRequirements::builder();
99    /// builder.dtype(true);
100    /// assert!(builder.build()?.specializes_dtype());
101    /// # Ok(())
102    /// # }
103    /// ```
104    pub fn specializes_dtype(&self) -> bool {
105        self.dtype
106    }
107
108    /// Return whether rank is specialized.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
114    /// use tenferro_runtime::InputSpecializationRequirements;
115    ///
116    /// let mut builder = InputSpecializationRequirements::builder();
117    /// builder.rank(true);
118    /// assert!(builder.build()?.specializes_rank());
119    /// # Ok(())
120    /// # }
121    /// ```
122    pub fn specializes_rank(&self) -> bool {
123        self.rank
124    }
125
126    /// Return concrete-dimension axes.
127    ///
128    /// # Examples
129    ///
130    /// ```
131    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
132    /// use tenferro_runtime::InputSpecializationRequirements;
133    ///
134    /// let mut builder = InputSpecializationRequirements::builder();
135    /// builder.rank(true).concrete_dimensions(vec![1]);
136    /// assert_eq!(builder.build()?.concrete_dimensions(), &[1]);
137    /// # Ok(())
138    /// # }
139    /// ```
140    pub fn concrete_dimensions(&self) -> &[u32] {
141        &self.concrete_dimensions
142    }
143
144    /// Return the placement specialization mode.
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
150    /// use tenferro_runtime::{InputSpecializationRequirements, PlacementSpecialization};
151    ///
152    /// let mut builder = InputSpecializationRequirements::builder();
153    /// builder.placement(PlacementSpecialization::Device);
154    /// assert_eq!(builder.build()?.placement(), PlacementSpecialization::Device);
155    /// # Ok(())
156    /// # }
157    /// ```
158    pub fn placement(&self) -> PlacementSpecialization {
159        self.placement
160    }
161
162    /// Return the layout specialization mode.
163    ///
164    /// # Examples
165    ///
166    /// ```
167    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
168    /// use tenferro_runtime::{InputSpecializationRequirements, LayoutSpecialization};
169    ///
170    /// let mut builder = InputSpecializationRequirements::builder();
171    /// builder.layout(LayoutSpecialization::Class);
172    /// assert_eq!(builder.build()?.layout(), LayoutSpecialization::Class);
173    /// # Ok(())
174    /// # }
175    /// ```
176    pub fn layout(&self) -> LayoutSpecialization {
177        self.layout
178    }
179
180    /// Return the required alignment class, if any.
181    ///
182    /// # Examples
183    ///
184    /// ```
185    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
186    /// use tenferro_runtime::InputSpecializationRequirements;
187    ///
188    /// let mut builder = InputSpecializationRequirements::builder();
189    /// builder.alignment_log2(Some(2));
190    /// assert_eq!(builder.build()?.alignment_log2(), Some(2));
191    /// # Ok(())
192    /// # }
193    /// ```
194    pub fn alignment_log2(&self) -> Option<u8> {
195        self.alignment_log2
196    }
197}
198
199/// Builder for per-input specialization requirements.
200///
201/// # Examples
202///
203/// ```
204/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
205/// use tenferro_runtime::InputSpecializationRequirementsBuilder;
206///
207/// let mut builder = InputSpecializationRequirementsBuilder::new();
208/// builder.dtype(true);
209/// assert!(builder.build()?.specializes_dtype());
210/// # Ok(())
211/// # }
212/// ```
213#[derive(Clone, Debug, Default, Eq, PartialEq)]
214pub struct InputSpecializationRequirementsBuilder {
215    dtype: bool,
216    rank: bool,
217    concrete_dimensions: Vec<u32>,
218    placement: PlacementSpecialization,
219    layout: LayoutSpecialization,
220    alignment_log2: Option<u8>,
221}
222
223impl InputSpecializationRequirementsBuilder {
224    /// Return a builder with no specialization fields enabled.
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
230    /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
231    ///
232    /// assert!(!InputSpecializationRequirementsBuilder::new()
233    ///     .build()
234    ///     ?
235    ///     .specializes_dtype());
236    /// # Ok(())
237    /// # }
238    /// ```
239    pub fn new() -> Self {
240        Self::default()
241    }
242
243    /// Set whether dtype should be specialized.
244    ///
245    /// # Examples
246    ///
247    /// ```
248    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
249    /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
250    ///
251    /// let mut builder = InputSpecializationRequirementsBuilder::new();
252    /// builder.dtype(true);
253    /// assert!(builder.build()?.specializes_dtype());
254    /// # Ok(())
255    /// # }
256    /// ```
257    pub fn dtype(&mut self, dtype: bool) -> &mut Self {
258        self.dtype = dtype;
259        self
260    }
261
262    /// Set whether rank should be specialized.
263    ///
264    /// # Examples
265    ///
266    /// ```
267    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
268    /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
269    ///
270    /// let mut builder = InputSpecializationRequirementsBuilder::new();
271    /// builder.rank(true);
272    /// assert!(builder.build()?.specializes_rank());
273    /// # Ok(())
274    /// # }
275    /// ```
276    pub fn rank(&mut self, rank: bool) -> &mut Self {
277        self.rank = rank;
278        self
279    }
280
281    /// Set concrete axes whose dimensions should be specialized.
282    ///
283    /// # Examples
284    ///
285    /// ```
286    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
287    /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
288    ///
289    /// let mut builder = InputSpecializationRequirementsBuilder::new();
290    /// builder.rank(true).concrete_dimensions(vec![0]);
291    /// assert_eq!(builder.build()?.concrete_dimensions(), &[0]);
292    /// # Ok(())
293    /// # }
294    /// ```
295    pub fn concrete_dimensions(&mut self, axes: impl Into<Vec<u32>>) -> &mut Self {
296        self.concrete_dimensions = axes.into();
297        self
298    }
299
300    /// Set placement specialization.
301    ///
302    /// # Examples
303    ///
304    /// ```
305    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
306    /// use tenferro_runtime::{
307    ///     InputSpecializationRequirementsBuilder, PlacementSpecialization,
308    /// };
309    ///
310    /// let mut builder = InputSpecializationRequirementsBuilder::new();
311    /// builder.placement(PlacementSpecialization::StorageClass);
312    /// assert_eq!(builder.build()?.placement(), PlacementSpecialization::StorageClass);
313    /// # Ok(())
314    /// # }
315    /// ```
316    pub fn placement(&mut self, placement: PlacementSpecialization) -> &mut Self {
317        self.placement = placement;
318        self
319    }
320
321    /// Set layout specialization.
322    ///
323    /// # Examples
324    ///
325    /// ```
326    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
327    /// use tenferro_runtime::{InputSpecializationRequirementsBuilder, LayoutSpecialization};
328    ///
329    /// let mut builder = InputSpecializationRequirementsBuilder::new();
330    /// builder.rank(true).layout(LayoutSpecialization::ExactStrides);
331    /// assert_eq!(builder.build()?.layout(), LayoutSpecialization::ExactStrides);
332    /// # Ok(())
333    /// # }
334    /// ```
335    pub fn layout(&mut self, layout: LayoutSpecialization) -> &mut Self {
336        self.layout = layout;
337        self
338    }
339
340    /// Set a required alignment class.
341    ///
342    /// # Examples
343    ///
344    /// ```
345    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
346    /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
347    ///
348    /// let mut builder = InputSpecializationRequirementsBuilder::new();
349    /// builder.alignment_log2(Some(1));
350    /// assert_eq!(builder.build()?.alignment_log2(), Some(1));
351    /// # Ok(())
352    /// # }
353    /// ```
354    pub fn alignment_log2(&mut self, alignment_log2: Option<u8>) -> &mut Self {
355        self.alignment_log2 = alignment_log2;
356        self
357    }
358
359    /// Validate and build per-input requirements.
360    ///
361    /// # Examples
362    ///
363    /// ```
364    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
365    /// use tenferro_runtime::InputSpecializationRequirementsBuilder;
366    ///
367    /// let requirements = InputSpecializationRequirementsBuilder::new().build()?;
368    /// assert!(requirements.concrete_dimensions().is_empty());
369    /// # Ok(())
370    /// # }
371    /// ```
372    ///
373    /// # Errors
374    ///
375    /// Returns [`InputSpecializationRequirementsError`] when the requested
376    /// fields are internally inconsistent or outside finite lattices.
377    pub fn build(
378        self,
379    ) -> Result<InputSpecializationRequirements, InputSpecializationRequirementsError> {
380        validate_unique_axes(&self.concrete_dimensions)?;
381        if !self.rank {
382            if let Some(&axis) = self.concrete_dimensions.first() {
383                return Err(InputSpecializationRequirementsError::RankRequired {
384                    reason: RankRequirement::ConcreteAxis { axis },
385                });
386            }
387            if self.layout == LayoutSpecialization::ExactStrides {
388                return Err(InputSpecializationRequirementsError::RankRequired {
389                    reason: RankRequirement::ExactStrides,
390                });
391            }
392        }
393        if let Some(alignment_log2) = self.alignment_log2 {
394            if u32::from(alignment_log2) >= usize::BITS {
395                return Err(
396                    InputSpecializationRequirementsError::InvalidAlignmentClass { alignment_log2 },
397                );
398            }
399        }
400        Ok(InputSpecializationRequirements {
401            dtype: self.dtype,
402            rank: self.rank,
403            concrete_dimensions: self.concrete_dimensions,
404            placement: self.placement,
405            layout: self.layout,
406            alignment_log2: self.alignment_log2,
407        })
408    }
409}
410
411/// Aggregate specialization requirements for all inputs.
412///
413/// # Examples
414///
415/// ```
416/// use tenferro_runtime::SpecializationRequirements;
417///
418/// assert_eq!(SpecializationRequirements::polymorphic(2).inputs().len(), 2);
419/// ```
420#[derive(Clone, Debug, Eq, Hash, PartialEq)]
421pub struct SpecializationRequirements {
422    inputs: Vec<InputSpecializationRequirements>,
423}
424
425impl SpecializationRequirements {
426    /// Build aggregate requirements from validated input requirements.
427    ///
428    /// # Examples
429    ///
430    /// ```
431    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
432    /// use tenferro_runtime::{InputSpecializationRequirements, SpecializationRequirements};
433    ///
434    /// let input = InputSpecializationRequirements::builder().build()?;
435    /// assert_eq!(SpecializationRequirements::new(vec![input]).inputs().len(), 1);
436    /// # Ok(())
437    /// # }
438    /// ```
439    pub fn new(inputs: impl Into<Vec<InputSpecializationRequirements>>) -> Self {
440        Self {
441            inputs: inputs.into(),
442        }
443    }
444
445    /// Build fully polymorphic requirements for `input_count` inputs.
446    ///
447    /// # Examples
448    ///
449    /// ```
450    /// use tenferro_runtime::SpecializationRequirements;
451    ///
452    /// assert_eq!(SpecializationRequirements::polymorphic(3).inputs().len(), 3);
453    /// ```
454    pub fn polymorphic(input_count: usize) -> Self {
455        let input = InputSpecializationRequirements {
456            dtype: false,
457            rank: false,
458            concrete_dimensions: Vec::new(),
459            placement: PlacementSpecialization::None,
460            layout: LayoutSpecialization::None,
461            alignment_log2: None,
462        };
463        Self {
464            inputs: vec![input; input_count],
465        }
466    }
467
468    /// Return per-input requirements.
469    ///
470    /// # Examples
471    ///
472    /// ```
473    /// use tenferro_runtime::SpecializationRequirements;
474    ///
475    /// assert!(SpecializationRequirements::polymorphic(0).inputs().is_empty());
476    /// ```
477    pub fn inputs(&self) -> &[InputSpecializationRequirements] {
478        &self.inputs
479    }
480
481    /// Project a concrete input signature through these requirements.
482    ///
483    /// # Examples
484    ///
485    /// ```
486    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
487    /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
488    ///
489    /// let projection = SpecializationRequirements::polymorphic(0)
490    ///     .project(&InputSignature::new(Vec::new()))
491    ///     ?;
492    /// assert!(projection.inputs().is_empty());
493    /// # Ok(())
494    /// # }
495    /// ```
496    ///
497    /// # Errors
498    ///
499    /// Returns [`PrepareError::Specialization`] when the signature arity does
500    /// not match, a concrete axis is outside the actual rank, or a required
501    /// alignment class is unavailable.
502    pub fn project(
503        &self,
504        signature: &InputSignature,
505    ) -> Result<SpecializationProjection, PrepareError> {
506        if self.inputs.len() != signature.entries().len() {
507            return Err(PrepareError::Specialization {
508                source: SpecializationError::WrongInputCount {
509                    expected: self.inputs.len(),
510                    actual: signature.entries().len(),
511                },
512            });
513        }
514
515        let mut inputs = Vec::with_capacity(self.inputs.len());
516        for (input, (requirements, entry)) in
517            self.inputs.iter().zip(signature.entries()).enumerate()
518        {
519            let concrete_dimensions =
520                project_concrete_dimensions(input, requirements, entry.shape())?;
521            let alignment_log2 = project_alignment(input, requirements, entry.alignment_log2())?;
522            inputs.push(InputSpecializationProjection {
523                dtype: requirements.specializes_dtype().then_some(entry.dtype()),
524                rank: requirements
525                    .specializes_rank()
526                    .then_some(entry.shape().len()),
527                concrete_dimensions,
528                placement: project_placement(requirements, entry.placement()),
529                layout: project_layout(requirements, entry.layout_class().clone(), entry.strides()),
530                alignment_log2,
531            });
532        }
533
534        Ok(SpecializationProjection {
535            requirements: self.clone(),
536            inputs,
537        })
538    }
539
540    /// Return whether `self` is strictly wider than `other`.
541    ///
542    /// # Examples
543    ///
544    /// ```
545    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
546    /// use tenferro_runtime::{InputSpecializationRequirements, SpecializationRequirements};
547    ///
548    /// let polymorphic = SpecializationRequirements::new(vec![
549    ///     InputSpecializationRequirements::builder().build()?,
550    /// ]);
551    /// let mut builder = InputSpecializationRequirements::builder();
552    /// builder.dtype(true);
553    /// let dtype = SpecializationRequirements::new(vec![builder.build()?]);
554    /// assert!(polymorphic.strictly_widens(&dtype));
555    /// # Ok(())
556    /// # }
557    /// ```
558    pub fn strictly_widens(&self, other: &Self) -> bool {
559        self.inputs.len() == other.inputs.len()
560            && self
561                .inputs
562                .iter()
563                .zip(&other.inputs)
564                .try_fold(false, |strict, (left, right)| {
565                    input_widening(left, right).map(|input_strict| strict || input_strict)
566                })
567                .unwrap_or(false)
568    }
569}
570
571/// Aggregate projection of a concrete signature.
572///
573/// # Examples
574///
575/// ```
576/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
577/// use tenferro_runtime::{InputSignature, SpecializationRequirements};
578///
579/// let projection = SpecializationRequirements::polymorphic(0)
580///     .project(&InputSignature::new(Vec::new()))
581///     ?;
582/// assert!(projection.inputs().is_empty());
583/// # Ok(())
584/// # }
585/// ```
586#[derive(Clone, Debug, Eq, Hash, PartialEq)]
587pub struct SpecializationProjection {
588    requirements: SpecializationRequirements,
589    inputs: Vec<InputSpecializationProjection>,
590}
591
592impl SpecializationProjection {
593    /// Return the requirements that produced this projection.
594    ///
595    /// # Examples
596    ///
597    /// ```
598    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
599    /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
600    ///
601    /// let requirements = SpecializationRequirements::polymorphic(0);
602    /// let projection = requirements.project(&InputSignature::new(Vec::new()))?;
603    /// assert_eq!(projection.requirements(), &requirements);
604    /// # Ok(())
605    /// # }
606    /// ```
607    pub fn requirements(&self) -> &SpecializationRequirements {
608        &self.requirements
609    }
610
611    /// Return per-input projections.
612    ///
613    /// # Examples
614    ///
615    /// ```
616    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
617    /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
618    ///
619    /// let projection = SpecializationRequirements::polymorphic(0)
620    ///     .project(&InputSignature::new(Vec::new()))
621    ///     ?;
622    /// assert!(projection.inputs().is_empty());
623    /// # Ok(())
624    /// # }
625    /// ```
626    pub fn inputs(&self) -> &[InputSpecializationProjection] {
627        &self.inputs
628    }
629}
630
631/// Projected specialization fields for one input.
632///
633/// # Examples
634///
635/// ```
636/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
637/// use tenferro_runtime::{InputSignature, SpecializationRequirements};
638///
639/// let projection = SpecializationRequirements::polymorphic(0)
640///     .project(&InputSignature::new(Vec::new()))
641///     ?;
642/// assert!(projection.inputs().is_empty());
643/// # Ok(())
644/// # }
645/// ```
646#[derive(Clone, Debug, Eq, Hash, PartialEq)]
647pub struct InputSpecializationProjection {
648    dtype: Option<DType>,
649    rank: Option<usize>,
650    concrete_dimensions: Vec<(u32, usize)>,
651    placement: Option<PlacementProjection>,
652    layout: Option<LayoutProjection>,
653    alignment_log2: Option<u8>,
654}
655
656impl InputSpecializationProjection {
657    /// Return projected dtype, if specialized.
658    ///
659    /// # Examples
660    ///
661    /// ```
662    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
663    /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
664    ///
665    /// let projection = SpecializationRequirements::polymorphic(0)
666    ///     .project(&InputSignature::new(Vec::new()))
667    ///     ?;
668    /// assert!(projection.inputs().is_empty());
669    /// # Ok(())
670    /// # }
671    /// ```
672    pub fn dtype(&self) -> Option<DType> {
673        self.dtype
674    }
675
676    /// Return projected rank, if specialized.
677    ///
678    /// # Examples
679    ///
680    /// ```
681    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
682    /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
683    ///
684    /// let projection = SpecializationRequirements::polymorphic(0)
685    ///     .project(&InputSignature::new(Vec::new()))
686    ///     ?;
687    /// assert!(projection.inputs().is_empty());
688    /// # Ok(())
689    /// # }
690    /// ```
691    pub fn rank(&self) -> Option<usize> {
692        self.rank
693    }
694
695    /// Return projected concrete dimensions.
696    ///
697    /// # Examples
698    ///
699    /// ```
700    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
701    /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
702    ///
703    /// let projection = SpecializationRequirements::polymorphic(0)
704    ///     .project(&InputSignature::new(Vec::new()))
705    ///     ?;
706    /// assert!(projection.inputs().is_empty());
707    /// # Ok(())
708    /// # }
709    /// ```
710    pub fn concrete_dimensions(&self) -> &[(u32, usize)] {
711        &self.concrete_dimensions
712    }
713
714    /// Return projected placement, if specialized.
715    ///
716    /// # Examples
717    ///
718    /// ```
719    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
720    /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
721    ///
722    /// let projection = SpecializationRequirements::polymorphic(0)
723    ///     .project(&InputSignature::new(Vec::new()))
724    ///     ?;
725    /// assert!(projection.inputs().is_empty());
726    /// # Ok(())
727    /// # }
728    /// ```
729    pub fn placement(&self) -> Option<&PlacementProjection> {
730        self.placement.as_ref()
731    }
732
733    /// Return projected layout, if specialized.
734    ///
735    /// # Examples
736    ///
737    /// ```
738    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
739    /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
740    ///
741    /// let projection = SpecializationRequirements::polymorphic(0)
742    ///     .project(&InputSignature::new(Vec::new()))
743    ///     ?;
744    /// assert!(projection.inputs().is_empty());
745    /// # Ok(())
746    /// # }
747    /// ```
748    pub fn layout(&self) -> Option<&LayoutProjection> {
749        self.layout.as_ref()
750    }
751
752    /// Return projected alignment class, if specialized.
753    ///
754    /// # Examples
755    ///
756    /// ```
757    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
758    /// use tenferro_runtime::{InputSignature, SpecializationRequirements};
759    ///
760    /// let projection = SpecializationRequirements::polymorphic(0)
761    ///     .project(&InputSignature::new(Vec::new()))
762    ///     ?;
763    /// assert!(projection.inputs().is_empty());
764    /// # Ok(())
765    /// # }
766    /// ```
767    pub fn alignment_log2(&self) -> Option<u8> {
768        self.alignment_log2
769    }
770}
771
772/// Projected placement specialization.
773///
774/// # Examples
775///
776/// ```
777/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
778/// use tenferro_runtime::{PlacementProjection, StorageClass};
779///
780/// let storage = StorageClass::new("tenferro.storage.host")?;
781/// let projection = PlacementProjection::StorageClass(storage.clone());
782/// assert_eq!(projection, PlacementProjection::StorageClass(storage));
783/// # Ok(())
784/// # }
785/// ```
786#[derive(Clone, Debug, Eq, Hash, PartialEq)]
787pub enum PlacementProjection {
788    /// Full device placement metadata.
789    Device(Placement),
790    /// Runtime-created storage class.
791    StorageClass(StorageClass),
792}
793
794/// Projected layout specialization.
795///
796/// # Examples
797///
798/// ```
799/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
800/// use tenferro_runtime::{LayoutClass, LayoutProjection};
801///
802/// let layout = LayoutClass::new("tenferro.layout.strided")?;
803/// let projection = LayoutProjection::Class(layout.clone());
804/// assert_eq!(projection, LayoutProjection::Class(layout));
805/// # Ok(())
806/// # }
807/// ```
808#[derive(Clone, Debug, Eq, Hash, PartialEq)]
809pub enum LayoutProjection {
810    /// Runtime-created layout class.
811    Class(super::LayoutClass),
812    /// Exact element strides.
813    ExactStrides(StrideVec),
814}
815
816fn validate_unique_axes(axes: &[u32]) -> Result<(), InputSpecializationRequirementsError> {
817    for duplicate_index in 0..axes.len() {
818        if let Some(first_index) =
819            (0..duplicate_index).find(|&first_index| axes[first_index] == axes[duplicate_index])
820        {
821            return Err(InputSpecializationRequirementsError::DuplicateAxis {
822                axis: axes[duplicate_index],
823                first_index,
824                duplicate_index,
825            });
826        }
827    }
828    Ok(())
829}
830
831fn project_concrete_dimensions(
832    input: usize,
833    requirements: &InputSpecializationRequirements,
834    shape: &[usize],
835) -> Result<Vec<(u32, usize)>, PrepareError> {
836    let mut projected = Vec::with_capacity(requirements.concrete_dimensions().len());
837    for &axis in requirements.concrete_dimensions() {
838        let axis_index = axis as usize;
839        let Some(&dimension) = shape.get(axis_index) else {
840            return Err(PrepareError::Specialization {
841                source: SpecializationError::AxisOutOfRange {
842                    input,
843                    axis,
844                    rank: shape.len(),
845                },
846            });
847        };
848        projected.push((axis, dimension));
849    }
850    Ok(projected)
851}
852
853fn project_alignment(
854    input: usize,
855    requirements: &InputSpecializationRequirements,
856    actual: Option<u8>,
857) -> Result<Option<u8>, PrepareError> {
858    match (requirements.alignment_log2(), actual) {
859        (None, _) => Ok(None),
860        (Some(required_alignment_log2), None) => Err(PrepareError::Specialization {
861            source: SpecializationError::AlignmentUnavailable {
862                input,
863                required_alignment_log2,
864            },
865        }),
866        (Some(required), Some(actual)) => Ok(Some(required.min(actual))),
867    }
868}
869
870fn project_placement(
871    requirements: &InputSpecializationRequirements,
872    placement: &Placement,
873) -> Option<PlacementProjection> {
874    match requirements.placement() {
875        PlacementSpecialization::None => None,
876        PlacementSpecialization::Device => Some(PlacementProjection::Device(placement.clone())),
877        PlacementSpecialization::StorageClass => Some(PlacementProjection::StorageClass(
878            storage_class(&placement.memory_kind),
879        )),
880    }
881}
882
883fn project_layout(
884    requirements: &InputSpecializationRequirements,
885    layout_class: super::LayoutClass,
886    strides: &[isize],
887) -> Option<LayoutProjection> {
888    match requirements.layout() {
889        LayoutSpecialization::None => None,
890        LayoutSpecialization::Class => Some(LayoutProjection::Class(layout_class)),
891        LayoutSpecialization::ExactStrides => Some(LayoutProjection::ExactStrides(
892            strides.iter().copied().collect(),
893        )),
894    }
895}
896
897fn storage_class(memory_kind: &MemoryKind) -> StorageClass {
898    match memory_kind {
899        MemoryKind::Device => StorageClass::runtime_created("tenferro.storage.device.v1"),
900        MemoryKind::PinnedHost => StorageClass::runtime_created("tenferro.storage.pinned-host.v1"),
901        MemoryKind::UnpinnedHost => {
902            StorageClass::runtime_created("tenferro.storage.unpinned-host.v1")
903        }
904        MemoryKind::Managed => StorageClass::runtime_created("tenferro.storage.managed.v1"),
905        MemoryKind::Other(payload) if payload.is_empty() => {
906            StorageClass::runtime_created("tenferro.storage.other-empty.v1")
907        }
908        MemoryKind::Other(payload) => StorageClass::runtime_created(other_utf8_storage_id(payload)),
909    }
910}
911
912fn other_utf8_storage_id(payload: &str) -> String {
913    let mut value =
914        String::with_capacity("tenferro.storage.other-utf8-.v1".len() + payload.len() * 2);
915    value.push_str("tenferro.storage.other-utf8-");
916    const HEX: &[u8; 16] = b"0123456789abcdef";
917    for byte in payload.as_bytes() {
918        value.push(HEX[(byte >> 4) as usize] as char);
919        value.push(HEX[(byte & 0x0f) as usize] as char);
920    }
921    value.push_str(".v1");
922    value
923}
924
925fn input_widening(
926    left: &InputSpecializationRequirements,
927    right: &InputSpecializationRequirements,
928) -> Option<bool> {
929    let mut strict = false;
930    strict |= bool_widens(left.specializes_dtype(), right.specializes_dtype())?;
931    strict |= bool_widens(left.specializes_rank(), right.specializes_rank())?;
932    strict |= axes_widen(left.concrete_dimensions(), right.concrete_dimensions())?;
933    strict |= placement_widens(left.placement(), right.placement())?;
934    strict |= layout_widens(left.layout(), right.layout())?;
935    strict |= alignment_widens(left.alignment_log2(), right.alignment_log2())?;
936    Some(strict)
937}
938
939fn bool_widens(left: bool, right: bool) -> Option<bool> {
940    match (left, right) {
941        (false, true) => Some(true),
942        (left, right) if left == right => Some(false),
943        _ => None,
944    }
945}
946
947fn axes_widen(left: &[u32], right: &[u32]) -> Option<bool> {
948    if left.iter().all(|axis| right.contains(axis)) {
949        Some(left.len() < right.len())
950    } else {
951        None
952    }
953}
954
955fn placement_widens(left: PlacementSpecialization, right: PlacementSpecialization) -> Option<bool> {
956    (left <= right).then_some(left < right)
957}
958
959fn layout_widens(left: LayoutSpecialization, right: LayoutSpecialization) -> Option<bool> {
960    let left_rank = layout_rank(left);
961    let right_rank = layout_rank(right);
962    (left_rank <= right_rank).then_some(left_rank < right_rank)
963}
964
965fn layout_rank(layout: LayoutSpecialization) -> u8 {
966    match layout {
967        LayoutSpecialization::None => 0,
968        LayoutSpecialization::Class => 1,
969        LayoutSpecialization::ExactStrides => 2,
970    }
971}
972
973fn alignment_widens(left: Option<u8>, right: Option<u8>) -> Option<bool> {
974    match (left, right) {
975        (None, None) => Some(false),
976        (None, Some(_)) => Some(true),
977        (Some(_), None) => None,
978        (Some(left), Some(right)) if left <= right => Some(left < right),
979        _ => None,
980    }
981}