naga/proc/
type_methods.rs

1//! Methods on or related to [`TypeInner`], [`Scalar`], [`ScalarKind`], and [`VectorSize`].
2//!
3//! [`TypeInner`]: crate::TypeInner
4//! [`Scalar`]: crate::Scalar
5//! [`ScalarKind`]: crate::ScalarKind
6//! [`VectorSize`]: crate::VectorSize
7
8use crate::{ir, valid::MAX_TYPE_SIZE};
9
10use super::TypeResolution;
11
12impl crate::ScalarKind {
13    pub const fn is_numeric(self) -> bool {
14        match self {
15            crate::ScalarKind::Sint
16            | crate::ScalarKind::Uint
17            | crate::ScalarKind::Float
18            | crate::ScalarKind::AbstractInt
19            | crate::ScalarKind::AbstractFloat => true,
20            crate::ScalarKind::Bool => false,
21        }
22    }
23}
24
25impl crate::Scalar {
26    pub const I32: Self = Self {
27        kind: crate::ScalarKind::Sint,
28        width: 4,
29    };
30    pub const U32: Self = Self {
31        kind: crate::ScalarKind::Uint,
32        width: 4,
33    };
34    pub const F16: Self = Self {
35        kind: crate::ScalarKind::Float,
36        width: 2,
37    };
38    pub const F32: Self = Self {
39        kind: crate::ScalarKind::Float,
40        width: 4,
41    };
42    pub const F64: Self = Self {
43        kind: crate::ScalarKind::Float,
44        width: 8,
45    };
46    pub const I64: Self = Self {
47        kind: crate::ScalarKind::Sint,
48        width: 8,
49    };
50    pub const U64: Self = Self {
51        kind: crate::ScalarKind::Uint,
52        width: 8,
53    };
54    pub const BOOL: Self = Self {
55        kind: crate::ScalarKind::Bool,
56        width: crate::BOOL_WIDTH,
57    };
58    pub const ABSTRACT_INT: Self = Self {
59        kind: crate::ScalarKind::AbstractInt,
60        width: crate::ABSTRACT_WIDTH,
61    };
62    pub const ABSTRACT_FLOAT: Self = Self {
63        kind: crate::ScalarKind::AbstractFloat,
64        width: crate::ABSTRACT_WIDTH,
65    };
66
67    pub const fn is_abstract(self) -> bool {
68        match self.kind {
69            crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => true,
70            crate::ScalarKind::Sint
71            | crate::ScalarKind::Uint
72            | crate::ScalarKind::Float
73            | crate::ScalarKind::Bool => false,
74        }
75    }
76
77    /// Construct a float `Scalar` with the given width.
78    ///
79    /// This is especially common when dealing with
80    /// `TypeInner::Matrix`, where the scalar kind is implicit.
81    pub const fn float(width: crate::Bytes) -> Self {
82        Self {
83            kind: crate::ScalarKind::Float,
84            width,
85        }
86    }
87
88    pub const fn to_inner_scalar(self) -> crate::TypeInner {
89        crate::TypeInner::Scalar(self)
90    }
91
92    pub const fn to_inner_vector(self, size: crate::VectorSize) -> crate::TypeInner {
93        crate::TypeInner::Vector { size, scalar: self }
94    }
95
96    pub const fn to_inner_atomic(self) -> crate::TypeInner {
97        crate::TypeInner::Atomic(self)
98    }
99}
100
101/// Produce all concrete integer [`ir::Scalar`]s.
102///
103/// Note that `I32` and `U32` must come first; this represents conversion rank
104/// in overload resolution.
105pub fn concrete_int_scalars() -> impl Iterator<Item = ir::Scalar> {
106    [
107        ir::Scalar::I32,
108        ir::Scalar::U32,
109        ir::Scalar::I64,
110        ir::Scalar::U64,
111    ]
112    .into_iter()
113}
114
115/// Produce all vector sizes.
116pub fn vector_sizes() -> impl Iterator<Item = ir::VectorSize> + Clone {
117    static SIZES: [ir::VectorSize; 3] = [
118        ir::VectorSize::Bi,
119        ir::VectorSize::Tri,
120        ir::VectorSize::Quad,
121    ];
122
123    SIZES.iter().cloned()
124}
125
126const POINTER_SPAN: u32 = 4;
127
128impl crate::TypeInner {
129    /// Return the scalar type of `self`.
130    ///
131    /// If `inner` is a scalar, vector, or matrix type, return
132    /// its scalar type. Otherwise, return `None`.
133    ///
134    /// Note that this doesn't inspect [`Array`] types, as required
135    /// for automatic conversions. For that, see [`scalar_for_conversions`].
136    ///
137    /// [`Array`]: crate::TypeInner::Array
138    /// [`scalar_for_conversions`]: crate::TypeInner::scalar_for_conversions
139    pub const fn scalar(&self) -> Option<crate::Scalar> {
140        use crate::TypeInner as Ti;
141        match *self {
142            Ti::Scalar(scalar) | Ti::Vector { scalar, .. } => Some(scalar),
143            Ti::Matrix { scalar, .. } => Some(scalar),
144            Ti::CooperativeMatrix { scalar, .. } => Some(scalar),
145            _ => None,
146        }
147    }
148
149    pub fn scalar_kind(&self) -> Option<crate::ScalarKind> {
150        self.scalar().map(|scalar| scalar.kind)
151    }
152
153    /// Returns the scalar width in bytes
154    pub fn scalar_width(&self) -> Option<u8> {
155        self.scalar().map(|scalar| scalar.width)
156    }
157
158    /// Return the leaf scalar type of `self`, as needed for automatic conversions.
159    ///
160    /// Unlike the [`scalar`] method, which only retrieves scalars for
161    /// [`Scalar`], [`Vector`], and [`Matrix`] this also looks into
162    /// [`Array`] types to find the leaf scalar.
163    ///
164    /// [`scalar`]: crate::TypeInner::scalar
165    /// [`Scalar`]: crate::TypeInner::Scalar
166    /// [`Vector`]: crate::TypeInner::Vector
167    /// [`Matrix`]: crate::TypeInner::Matrix
168    /// [`Array`]: crate::TypeInner::Array
169    pub fn scalar_for_conversions(
170        &self,
171        types: &crate::UniqueArena<crate::Type>,
172    ) -> Option<crate::Scalar> {
173        use crate::TypeInner as Ti;
174        match *self {
175            Ti::Scalar(scalar) | Ti::Vector { scalar, .. } | Ti::Matrix { scalar, .. } => {
176                Some(scalar)
177            }
178            Ti::Array { base, .. } => types[base].inner.scalar_for_conversions(types),
179            _ => None,
180        }
181    }
182
183    pub const fn pointer_space(&self) -> Option<crate::AddressSpace> {
184        match *self {
185            Self::Pointer { space, .. } => Some(space),
186            Self::ValuePointer { space, .. } => Some(space),
187            _ => None,
188        }
189    }
190
191    /// If `self` is a pointer type, return its base type.
192    pub const fn pointer_base_type(&self) -> Option<TypeResolution> {
193        match *self {
194            crate::TypeInner::Pointer { base, .. } => Some(TypeResolution::Handle(base)),
195            crate::TypeInner::ValuePointer {
196                size: None, scalar, ..
197            } => Some(TypeResolution::Value(crate::TypeInner::Scalar(scalar))),
198            crate::TypeInner::ValuePointer {
199                size: Some(size),
200                scalar,
201                ..
202            } => Some(TypeResolution::Value(crate::TypeInner::Vector {
203                size,
204                scalar,
205            })),
206            _ => None,
207        }
208    }
209
210    pub fn is_atomic_pointer(&self, types: &crate::UniqueArena<crate::Type>) -> bool {
211        match *self {
212            Self::Pointer { base, .. } => match types[base].inner {
213                Self::Atomic { .. } => true,
214                _ => false,
215            },
216            _ => false,
217        }
218    }
219
220    /// Attempt to calculate the size of this type. Returns `None` if the size
221    /// exceeds the limit of [`crate::valid::MAX_TYPE_SIZE`].
222    pub fn try_size(&self, gctx: super::GlobalCtx) -> Option<u32> {
223        match *self {
224            Self::Scalar(scalar) | Self::Atomic(scalar) => Some(scalar.width as u32),
225            Self::Vector { size, scalar } => Some(size as u32 * scalar.width as u32),
226            // matrices are treated as arrays of aligned columns
227            Self::Matrix {
228                columns,
229                rows,
230                scalar,
231            } => Some(super::Alignment::from(rows) * scalar.width as u32 * columns as u32),
232            Self::CooperativeMatrix {
233                columns,
234                rows,
235                scalar,
236                role: _,
237            } => Some(columns as u32 * rows as u32 * scalar.width as u32),
238            Self::Pointer { .. } | Self::ValuePointer { .. } => Some(POINTER_SPAN),
239            Self::Array {
240                base: _,
241                size,
242                stride,
243            } => {
244                let count = match size.resolve(gctx) {
245                    Ok(crate::proc::IndexableLength::Known(count)) => count,
246                    // any struct member or array element needing a size at pipeline-creation time
247                    // must have a creation-fixed footprint
248                    Err(_) => 0,
249                    // A dynamically-sized array has to have at least one element
250                    Ok(crate::proc::IndexableLength::Dynamic) => 1,
251                };
252                if count > MAX_TYPE_SIZE {
253                    // It shouldn't be possible to have an array of a zero-sized type, but
254                    // let's check just in case.
255                    None
256                } else {
257                    count
258                        .checked_mul(stride)
259                        .filter(|size| *size <= MAX_TYPE_SIZE)
260                }
261            }
262            Self::Struct { span, .. } => Some(span),
263            Self::Image { .. }
264            | Self::Sampler { .. }
265            | Self::AccelerationStructure { .. }
266            | Self::RayQuery { .. }
267            | Self::BindingArray { .. } => Some(0),
268        }
269    }
270
271    /// Get the size of this type.
272    ///
273    /// Panics if the size exceeds the limit of [`crate::valid::MAX_TYPE_SIZE`].
274    /// Validated modules should not contain such types. Code working with
275    /// modules prior to validation should use [`Self::try_size`] and handle the
276    /// error appropriately.
277    pub fn size(&self, gctx: super::GlobalCtx) -> u32 {
278        self.try_size(gctx).expect("type is too large")
279    }
280
281    /// Return the canonical form of `self`, or `None` if it's already in
282    /// canonical form.
283    ///
284    /// Certain types have multiple representations in `TypeInner`. This
285    /// function converts all forms of equivalent types to a single
286    /// representative of their class, so that simply applying `Eq` to the
287    /// result indicates whether the types are equivalent, as far as Naga IR is
288    /// concerned.
289    pub fn canonical_form(
290        &self,
291        types: &crate::UniqueArena<crate::Type>,
292    ) -> Option<crate::TypeInner> {
293        use crate::TypeInner as Ti;
294        match *self {
295            Ti::Pointer { base, space } => match types[base].inner {
296                Ti::Scalar(scalar) => Some(Ti::ValuePointer {
297                    size: None,
298                    scalar,
299                    space,
300                }),
301                Ti::Vector { size, scalar } => Some(Ti::ValuePointer {
302                    size: Some(size),
303                    scalar,
304                    space,
305                }),
306                _ => None,
307            },
308            _ => None,
309        }
310    }
311
312    /// Compare value type `self` and `rhs` as types.
313    ///
314    /// This is mostly the same as `<TypeInner as Eq>::eq`, but it treats
315    /// [`ValuePointer`] and [`Pointer`] types as equivalent. This method
316    /// cannot be used for structs, because it cannot distinguish two
317    /// structs with different names but the same members. For structs,
318    /// use [`compare_types`].
319    ///
320    /// When you know that one side of the comparison is never a pointer or
321    /// struct, it's fine to not bother with canonicalization, and just
322    /// compare `TypeInner` values with `==`.
323    ///
324    /// # Panics
325    ///
326    /// If both `self` and `rhs` are structs.
327    ///
328    /// [`compare_types`]: crate::proc::compare_types
329    /// [`ValuePointer`]: ir::TypeInner::ValuePointer
330    /// [`Pointer`]: ir::TypeInner::Pointer
331    pub fn non_struct_equivalent(
332        &self,
333        rhs: &ir::TypeInner,
334        types: &crate::UniqueArena<crate::Type>,
335    ) -> bool {
336        let left = self.canonical_form(types);
337        let right = rhs.canonical_form(types);
338
339        let left_struct = matches!(*self, ir::TypeInner::Struct { .. });
340        let right_struct = matches!(*rhs, ir::TypeInner::Struct { .. });
341
342        assert!(!left_struct || !right_struct);
343
344        left.as_ref().unwrap_or(self) == right.as_ref().unwrap_or(rhs)
345    }
346
347    pub fn is_dynamically_sized(&self, types: &crate::UniqueArena<crate::Type>) -> bool {
348        use crate::TypeInner as Ti;
349        match *self {
350            Ti::Array { size, .. } => size == crate::ArraySize::Dynamic,
351            Ti::Struct { ref members, .. } => members
352                .last()
353                .map(|last| types[last.ty].inner.is_dynamically_sized(types))
354                .unwrap_or(false),
355            _ => false,
356        }
357    }
358
359    pub fn components(&self) -> Option<u32> {
360        Some(match *self {
361            Self::Vector { size, .. } => size as u32,
362            Self::Matrix { columns, .. } => columns as u32,
363            Self::Array {
364                size: crate::ArraySize::Constant(len),
365                ..
366            } => len.get(),
367            Self::Struct { ref members, .. } => members.len() as u32,
368            _ => return None,
369        })
370    }
371
372    pub fn component_type(&self, index: usize) -> Option<TypeResolution> {
373        Some(match *self {
374            Self::Vector { scalar, .. } => TypeResolution::Value(crate::TypeInner::Scalar(scalar)),
375            Self::Matrix { rows, scalar, .. } => {
376                TypeResolution::Value(crate::TypeInner::Vector { size: rows, scalar })
377            }
378            Self::Array {
379                base,
380                size: crate::ArraySize::Constant(_),
381                ..
382            } => TypeResolution::Handle(base),
383            Self::Struct { ref members, .. } => TypeResolution::Handle(members[index].ty),
384            _ => return None,
385        })
386    }
387
388    /// If the type is a Vector or a Scalar return a tuple of the vector size (or None
389    /// for Scalars), and the scalar kind. Returns (None, None) for other types.
390    pub const fn vector_size_and_scalar(
391        &self,
392    ) -> Option<(Option<crate::VectorSize>, crate::Scalar)> {
393        match *self {
394            crate::TypeInner::Scalar(scalar) => Some((None, scalar)),
395            crate::TypeInner::Vector { size, scalar } => Some((Some(size), scalar)),
396            crate::TypeInner::Matrix { .. }
397            | crate::TypeInner::CooperativeMatrix { .. }
398            | crate::TypeInner::Atomic(_)
399            | crate::TypeInner::Pointer { .. }
400            | crate::TypeInner::ValuePointer { .. }
401            | crate::TypeInner::Array { .. }
402            | crate::TypeInner::Struct { .. }
403            | crate::TypeInner::Image { .. }
404            | crate::TypeInner::Sampler { .. }
405            | crate::TypeInner::AccelerationStructure { .. }
406            | crate::TypeInner::RayQuery { .. }
407            | crate::TypeInner::BindingArray { .. } => None,
408        }
409    }
410
411    /// Return true if `self` is an abstract type.
412    ///
413    /// Use `types` to look up type handles. This is necessary to
414    /// recognize abstract arrays.
415    pub fn is_abstract(&self, types: &crate::UniqueArena<crate::Type>) -> bool {
416        match *self {
417            crate::TypeInner::Scalar(scalar)
418            | crate::TypeInner::Vector { scalar, .. }
419            | crate::TypeInner::Matrix { scalar, .. }
420            | crate::TypeInner::Atomic(scalar) => scalar.is_abstract(),
421            crate::TypeInner::Array { base, .. } => types[base].inner.is_abstract(types),
422            crate::TypeInner::CooperativeMatrix { .. }
423            | crate::TypeInner::ValuePointer { .. }
424            | crate::TypeInner::Pointer { .. }
425            | crate::TypeInner::Struct { .. }
426            | crate::TypeInner::Image { .. }
427            | crate::TypeInner::Sampler { .. }
428            | crate::TypeInner::AccelerationStructure { .. }
429            | crate::TypeInner::RayQuery { .. }
430            | crate::TypeInner::BindingArray { .. } => false,
431        }
432    }
433
434    /// Determine whether `self` automatically converts to `goal`.
435    ///
436    /// If Naga IR's automatic conversions will convert `self` to
437    /// `goal`, then return a pair `(from, to)`, where `from` and `to`
438    /// are the scalar types of the leaf values of `self` and `goal`.
439    ///
440    /// If `self` and `goal` are the same type, this will simply return
441    /// a pair `(S, S)`.
442    ///
443    /// If the automatic conversions cannot convert `self` to `goal`,
444    /// return `None`.
445    ///
446    /// Naga IR's automatic conversions will convert:
447    ///
448    /// - [`AbstractInt`] scalars to [`AbstractFloat`] or any numeric scalar type
449    ///
450    /// - [`AbstractFloat`] scalars to any floating-point scalar type
451    ///
452    /// - A [`Vector`] `{ size, scalar: S }` to `{ size, scalar: T }`
453    ///   if they would convert `S` to `T`
454    ///
455    /// - An [`Array`] `{ base: S, size, stride }` to `{ base: T, size, stride }`
456    ///   if they would convert `S` to `T`
457    ///
458    /// [`AbstractInt`]: crate::ScalarKind::AbstractInt
459    /// [`AbstractFloat`]: crate::ScalarKind::AbstractFloat
460    /// [`Vector`]: crate::TypeInner::Vector
461    /// [`Array`]: crate::TypeInner::Array
462    pub fn automatically_converts_to(
463        &self,
464        goal: &Self,
465        types: &crate::UniqueArena<crate::Type>,
466    ) -> Option<(crate::Scalar, crate::Scalar)> {
467        use crate::ScalarKind as Sk;
468        use crate::TypeInner as Ti;
469
470        // Automatic conversions only change the scalar type of a value's leaves
471        // (e.g., `vec4<AbstractFloat>` to `vec4<f32>`), never the type
472        // constructors applied to those scalar types (e.g., never scalar to
473        // `vec4`, or `vec2` to `vec3`). So first we check that the type
474        // constructors match, extracting the leaf scalar types in the process.
475        let expr_scalar;
476        let goal_scalar;
477        match (self, goal) {
478            (&Ti::Scalar(expr), &Ti::Scalar(goal)) => {
479                expr_scalar = expr;
480                goal_scalar = goal;
481            }
482            (
483                &Ti::Vector {
484                    size: expr_size,
485                    scalar: expr,
486                },
487                &Ti::Vector {
488                    size: goal_size,
489                    scalar: goal,
490                },
491            ) if expr_size == goal_size => {
492                expr_scalar = expr;
493                goal_scalar = goal;
494            }
495            (
496                &Ti::Matrix {
497                    rows: expr_rows,
498                    columns: expr_columns,
499                    scalar: expr,
500                },
501                &Ti::Matrix {
502                    rows: goal_rows,
503                    columns: goal_columns,
504                    scalar: goal,
505                },
506            ) if expr_rows == goal_rows && expr_columns == goal_columns => {
507                expr_scalar = expr;
508                goal_scalar = goal;
509            }
510            (
511                &Ti::Array {
512                    base: expr_base,
513                    size: expr_size,
514                    stride: _,
515                },
516                &Ti::Array {
517                    base: goal_base,
518                    size: goal_size,
519                    stride: _,
520                },
521            ) if expr_size == goal_size => {
522                return types[expr_base]
523                    .inner
524                    .automatically_converts_to(&types[goal_base].inner, types);
525            }
526            _ => return None,
527        }
528
529        match (expr_scalar.kind, goal_scalar.kind) {
530            (Sk::AbstractFloat, Sk::Float) => {}
531            (Sk::AbstractInt, Sk::Sint | Sk::Uint | Sk::AbstractFloat | Sk::Float) => {}
532            _ => return None,
533        }
534
535        log::trace!("      okay: expr {expr_scalar:?}, goal {goal_scalar:?}");
536        Some((expr_scalar, goal_scalar))
537    }
538}
539
540/// Helper trait for providing the min and max values exactly representable by
541/// the integer type `Self` and floating point type `F`.
542pub trait IntFloatLimits<F>
543where
544    F: num_traits::Float,
545{
546    /// Returns the minimum value exactly representable by the integer type
547    /// `Self` and floating point type `F`.
548    fn min_float() -> F;
549    /// Returns the maximum value exactly representable by the integer type
550    /// `Self` and floating point type `F`.
551    fn max_float() -> F;
552}
553
554macro_rules! define_int_float_limits {
555    ($int:ty, $float:ty, $min:expr, $max:expr) => {
556        impl IntFloatLimits<$float> for $int {
557            fn min_float() -> $float {
558                $min
559            }
560            fn max_float() -> $float {
561                $max
562            }
563        }
564    };
565}
566
567define_int_float_limits!(i32, half::f16, half::f16::MIN, half::f16::MAX);
568define_int_float_limits!(u32, half::f16, half::f16::ZERO, half::f16::MAX);
569define_int_float_limits!(i64, half::f16, half::f16::MIN, half::f16::MAX);
570define_int_float_limits!(u64, half::f16, half::f16::ZERO, half::f16::MAX);
571define_int_float_limits!(i32, f32, -2147483648.0f32, 2147483520.0f32);
572define_int_float_limits!(u32, f32, 0.0f32, 4294967040.0f32);
573define_int_float_limits!(
574    i64,
575    f32,
576    -9223372036854775808.0f32,
577    9223371487098961920.0f32
578);
579define_int_float_limits!(u64, f32, 0.0f32, 18446742974197923840.0f32);
580define_int_float_limits!(i32, f64, -2147483648.0f64, 2147483647.0f64);
581define_int_float_limits!(u32, f64, 0.0f64, 4294967295.0f64);
582define_int_float_limits!(
583    i64,
584    f64,
585    -9223372036854775808.0f64,
586    9223372036854774784.0f64
587);
588define_int_float_limits!(u64, f64, 0.0f64, 18446744073709549568.0f64);
589
590/// Returns a tuple of [`crate::Literal`]s representing the minimum and maximum
591/// float values exactly representable by the provided float and integer types.
592/// Panics if `float` is not one of `F16`, `F32`, or `F64`, or `int` is
593/// not one of `I32`, `U32`, `I64`, or `U64`.
594pub fn min_max_float_representable_by(
595    float: crate::Scalar,
596    int: crate::Scalar,
597) -> (crate::Literal, crate::Literal) {
598    match (float, int) {
599        (crate::Scalar::F16, crate::Scalar::I32) => (
600            crate::Literal::F16(i32::min_float()),
601            crate::Literal::F16(i32::max_float()),
602        ),
603        (crate::Scalar::F16, crate::Scalar::U32) => (
604            crate::Literal::F16(u32::min_float()),
605            crate::Literal::F16(u32::max_float()),
606        ),
607        (crate::Scalar::F16, crate::Scalar::I64) => (
608            crate::Literal::F16(i64::min_float()),
609            crate::Literal::F16(i64::max_float()),
610        ),
611        (crate::Scalar::F16, crate::Scalar::U64) => (
612            crate::Literal::F16(u64::min_float()),
613            crate::Literal::F16(u64::max_float()),
614        ),
615        (crate::Scalar::F32, crate::Scalar::I32) => (
616            crate::Literal::F32(i32::min_float()),
617            crate::Literal::F32(i32::max_float()),
618        ),
619        (crate::Scalar::F32, crate::Scalar::U32) => (
620            crate::Literal::F32(u32::min_float()),
621            crate::Literal::F32(u32::max_float()),
622        ),
623        (crate::Scalar::F32, crate::Scalar::I64) => (
624            crate::Literal::F32(i64::min_float()),
625            crate::Literal::F32(i64::max_float()),
626        ),
627        (crate::Scalar::F32, crate::Scalar::U64) => (
628            crate::Literal::F32(u64::min_float()),
629            crate::Literal::F32(u64::max_float()),
630        ),
631        (crate::Scalar::F64, crate::Scalar::I32) => (
632            crate::Literal::F64(i32::min_float()),
633            crate::Literal::F64(i32::max_float()),
634        ),
635        (crate::Scalar::F64, crate::Scalar::U32) => (
636            crate::Literal::F64(u32::min_float()),
637            crate::Literal::F64(u32::max_float()),
638        ),
639        (crate::Scalar::F64, crate::Scalar::I64) => (
640            crate::Literal::F64(i64::min_float()),
641            crate::Literal::F64(i64::max_float()),
642        ),
643        (crate::Scalar::F64, crate::Scalar::U64) => (
644            crate::Literal::F64(u64::min_float()),
645            crate::Literal::F64(u64::max_float()),
646        ),
647        _ => unreachable!(),
648    }
649}
650
651/// Helper function that returns the string corresponding to the [`VectorSize`](crate::VectorSize)
652pub const fn vector_size_str(size: crate::VectorSize) -> &'static str {
653    match size {
654        crate::VectorSize::Bi => "2",
655        crate::VectorSize::Tri => "3",
656        crate::VectorSize::Quad => "4",
657    }
658}