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    /// Returns true if `self` is runtime- or override-sized.
348    pub fn is_dynamically_sized(&self, types: &crate::UniqueArena<crate::Type>) -> bool {
349        use crate::TypeInner as Ti;
350        match *self {
351            Ti::Array {
352                size: crate::ArraySize::Constant(_),
353                ..
354            } => false,
355            Ti::Array {
356                size: crate::ArraySize::Pending(_) | crate::ArraySize::Dynamic,
357                ..
358            } => true,
359            Ti::Struct { ref members, .. } => members
360                .last()
361                .map(|last| types[last.ty].inner.is_dynamically_sized(types))
362                .unwrap_or(false),
363            _ => false,
364        }
365    }
366
367    /// Returns true if `self` is a constructible type.
368    pub fn is_constructible(&self, types: &crate::UniqueArena<crate::Type>) -> bool {
369        use crate::TypeInner as Ti;
370        match *self {
371            Ti::Array { base, size, .. } => {
372                let fixed_size = match size {
373                    ir::ArraySize::Constant(_) => true,
374                    ir::ArraySize::Pending(_) | ir::ArraySize::Dynamic => false,
375                };
376                fixed_size && types[base].inner.is_constructible(types)
377            }
378            Ti::Struct { ref members, .. } => members
379                .iter()
380                .all(|member| types[member.ty].inner.is_constructible(types)),
381            Ti::Atomic(_)
382            | Ti::Pointer { .. }
383            | Ti::ValuePointer { .. }
384            | Ti::Image { .. }
385            | Ti::Sampler { .. }
386            | Ti::AccelerationStructure { .. }
387            | Ti::BindingArray { .. } => false,
388            Ti::Scalar(_)
389            | Ti::Vector { .. }
390            | Ti::Matrix { .. }
391            | Ti::RayQuery { .. }
392            | Ti::CooperativeMatrix { .. } => true,
393        }
394    }
395
396    pub const fn components(&self) -> Option<u32> {
397        Some(match *self {
398            Self::Vector { size, .. } => size as u32,
399            Self::Matrix { columns, .. } => columns as u32,
400            Self::Array {
401                size: crate::ArraySize::Constant(len),
402                ..
403            } => len.get(),
404            Self::Struct { ref members, .. } => members.len() as u32,
405            _ => return None,
406        })
407    }
408
409    pub fn component_type(&self, index: usize) -> Option<TypeResolution> {
410        Some(match *self {
411            Self::Vector { scalar, .. } => TypeResolution::Value(crate::TypeInner::Scalar(scalar)),
412            Self::Matrix { rows, scalar, .. } => {
413                TypeResolution::Value(crate::TypeInner::Vector { size: rows, scalar })
414            }
415            Self::Array {
416                base,
417                size: crate::ArraySize::Constant(_),
418                ..
419            } => TypeResolution::Handle(base),
420            Self::Struct { ref members, .. } => TypeResolution::Handle(members[index].ty),
421            _ => return None,
422        })
423    }
424
425    /// If the type is a Vector or a Scalar return a tuple of the vector size (or None
426    /// for Scalars), and the scalar kind. Returns (None, None) for other types.
427    pub const fn vector_size_and_scalar(
428        &self,
429    ) -> Option<(Option<crate::VectorSize>, crate::Scalar)> {
430        match *self {
431            crate::TypeInner::Scalar(scalar) => Some((None, scalar)),
432            crate::TypeInner::Vector { size, scalar } => Some((Some(size), scalar)),
433            crate::TypeInner::Matrix { .. }
434            | crate::TypeInner::CooperativeMatrix { .. }
435            | crate::TypeInner::Atomic(_)
436            | crate::TypeInner::Pointer { .. }
437            | crate::TypeInner::ValuePointer { .. }
438            | crate::TypeInner::Array { .. }
439            | crate::TypeInner::Struct { .. }
440            | crate::TypeInner::Image { .. }
441            | crate::TypeInner::Sampler { .. }
442            | crate::TypeInner::AccelerationStructure { .. }
443            | crate::TypeInner::RayQuery { .. }
444            | crate::TypeInner::BindingArray { .. } => None,
445        }
446    }
447
448    /// Return true if `self` is an abstract type.
449    ///
450    /// Use `types` to look up type handles. This is necessary to
451    /// recognize abstract arrays.
452    pub fn is_abstract(&self, types: &crate::UniqueArena<crate::Type>) -> bool {
453        match *self {
454            crate::TypeInner::Scalar(scalar)
455            | crate::TypeInner::Vector { scalar, .. }
456            | crate::TypeInner::Matrix { scalar, .. }
457            | crate::TypeInner::Atomic(scalar) => scalar.is_abstract(),
458            crate::TypeInner::Array { base, .. } => types[base].inner.is_abstract(types),
459            crate::TypeInner::CooperativeMatrix { .. }
460            | crate::TypeInner::ValuePointer { .. }
461            | crate::TypeInner::Pointer { .. }
462            | crate::TypeInner::Struct { .. }
463            | crate::TypeInner::Image { .. }
464            | crate::TypeInner::Sampler { .. }
465            | crate::TypeInner::AccelerationStructure { .. }
466            | crate::TypeInner::RayQuery { .. }
467            | crate::TypeInner::BindingArray { .. } => false,
468        }
469    }
470
471    /// Determine whether `self` automatically converts to `goal`.
472    ///
473    /// If Naga IR's automatic conversions will convert `self` to
474    /// `goal`, then return a pair `(from, to)`, where `from` and `to`
475    /// are the scalar types of the leaf values of `self` and `goal`.
476    ///
477    /// If `self` and `goal` are the same type, this will simply return
478    /// a pair `(S, S)`.
479    ///
480    /// If the automatic conversions cannot convert `self` to `goal`,
481    /// return `None`.
482    ///
483    /// Naga IR's automatic conversions will convert:
484    ///
485    /// - [`AbstractInt`] scalars to [`AbstractFloat`] or any numeric scalar type
486    ///
487    /// - [`AbstractFloat`] scalars to any floating-point scalar type
488    ///
489    /// - A [`Vector`] `{ size, scalar: S }` to `{ size, scalar: T }`
490    ///   if they would convert `S` to `T`
491    ///
492    /// - An [`Array`] `{ base: S, size, stride }` to `{ base: T, size, stride }`
493    ///   if they would convert `S` to `T`
494    ///
495    /// [`AbstractInt`]: crate::ScalarKind::AbstractInt
496    /// [`AbstractFloat`]: crate::ScalarKind::AbstractFloat
497    /// [`Vector`]: crate::TypeInner::Vector
498    /// [`Array`]: crate::TypeInner::Array
499    pub fn automatically_converts_to(
500        &self,
501        goal: &Self,
502        types: &crate::UniqueArena<crate::Type>,
503    ) -> Option<(crate::Scalar, crate::Scalar)> {
504        use crate::ScalarKind as Sk;
505        use crate::TypeInner as Ti;
506
507        // Automatic conversions only change the scalar type of a value's leaves
508        // (e.g., `vec4<AbstractFloat>` to `vec4<f32>`), never the type
509        // constructors applied to those scalar types (e.g., never scalar to
510        // `vec4`, or `vec2` to `vec3`). So first we check that the type
511        // constructors match, extracting the leaf scalar types in the process.
512        let expr_scalar;
513        let goal_scalar;
514        match (self, goal) {
515            (&Ti::Scalar(expr), &Ti::Scalar(goal)) => {
516                expr_scalar = expr;
517                goal_scalar = goal;
518            }
519            (
520                &Ti::Vector {
521                    size: expr_size,
522                    scalar: expr,
523                },
524                &Ti::Vector {
525                    size: goal_size,
526                    scalar: goal,
527                },
528            ) if expr_size == goal_size => {
529                expr_scalar = expr;
530                goal_scalar = goal;
531            }
532            (
533                &Ti::Matrix {
534                    rows: expr_rows,
535                    columns: expr_columns,
536                    scalar: expr,
537                },
538                &Ti::Matrix {
539                    rows: goal_rows,
540                    columns: goal_columns,
541                    scalar: goal,
542                },
543            ) if expr_rows == goal_rows && expr_columns == goal_columns => {
544                expr_scalar = expr;
545                goal_scalar = goal;
546            }
547            (
548                &Ti::Array {
549                    base: expr_base,
550                    size: expr_size,
551                    stride: _,
552                },
553                &Ti::Array {
554                    base: goal_base,
555                    size: goal_size,
556                    stride: _,
557                },
558            ) if expr_size == goal_size => {
559                return types[expr_base]
560                    .inner
561                    .automatically_converts_to(&types[goal_base].inner, types);
562            }
563            _ => return None,
564        }
565
566        match (expr_scalar.kind, goal_scalar.kind) {
567            (Sk::AbstractFloat, Sk::Float) => {}
568            (Sk::AbstractInt, Sk::Sint | Sk::Uint | Sk::AbstractFloat | Sk::Float) => {}
569            _ => return None,
570        }
571
572        log::trace!("      okay: expr {expr_scalar:?}, goal {goal_scalar:?}");
573        Some((expr_scalar, goal_scalar))
574    }
575}
576
577/// Helper trait for providing the min and max values exactly representable by
578/// the integer type `Self` and floating point type `F`.
579pub trait IntFloatLimits<F>
580where
581    F: num_traits::Float,
582{
583    /// Returns the minimum value exactly representable by the integer type
584    /// `Self` and floating point type `F`.
585    fn min_float() -> F;
586    /// Returns the maximum value exactly representable by the integer type
587    /// `Self` and floating point type `F`.
588    fn max_float() -> F;
589}
590
591macro_rules! define_int_float_limits {
592    ($int:ty, $float:ty, $min:expr, $max:expr) => {
593        impl IntFloatLimits<$float> for $int {
594            fn min_float() -> $float {
595                $min
596            }
597            fn max_float() -> $float {
598                $max
599            }
600        }
601    };
602}
603
604define_int_float_limits!(i32, half::f16, half::f16::MIN, half::f16::MAX);
605define_int_float_limits!(u32, half::f16, half::f16::ZERO, half::f16::MAX);
606define_int_float_limits!(i64, half::f16, half::f16::MIN, half::f16::MAX);
607define_int_float_limits!(u64, half::f16, half::f16::ZERO, half::f16::MAX);
608define_int_float_limits!(i32, f32, -2147483648.0f32, 2147483520.0f32);
609define_int_float_limits!(u32, f32, 0.0f32, 4294967040.0f32);
610define_int_float_limits!(
611    i64,
612    f32,
613    -9223372036854775808.0f32,
614    9223371487098961920.0f32
615);
616define_int_float_limits!(u64, f32, 0.0f32, 18446742974197923840.0f32);
617define_int_float_limits!(i32, f64, -2147483648.0f64, 2147483647.0f64);
618define_int_float_limits!(u32, f64, 0.0f64, 4294967295.0f64);
619define_int_float_limits!(
620    i64,
621    f64,
622    -9223372036854775808.0f64,
623    9223372036854774784.0f64
624);
625define_int_float_limits!(u64, f64, 0.0f64, 18446744073709549568.0f64);
626
627/// Returns a tuple of [`crate::Literal`]s representing the minimum and maximum
628/// float values exactly representable by the provided float and integer types.
629/// Panics if `float` is not one of `F16`, `F32`, or `F64`, or `int` is
630/// not one of `I32`, `U32`, `I64`, or `U64`.
631pub fn min_max_float_representable_by(
632    float: crate::Scalar,
633    int: crate::Scalar,
634) -> (crate::Literal, crate::Literal) {
635    match (float, int) {
636        (crate::Scalar::F16, crate::Scalar::I32) => (
637            crate::Literal::F16(i32::min_float()),
638            crate::Literal::F16(i32::max_float()),
639        ),
640        (crate::Scalar::F16, crate::Scalar::U32) => (
641            crate::Literal::F16(u32::min_float()),
642            crate::Literal::F16(u32::max_float()),
643        ),
644        (crate::Scalar::F16, crate::Scalar::I64) => (
645            crate::Literal::F16(i64::min_float()),
646            crate::Literal::F16(i64::max_float()),
647        ),
648        (crate::Scalar::F16, crate::Scalar::U64) => (
649            crate::Literal::F16(u64::min_float()),
650            crate::Literal::F16(u64::max_float()),
651        ),
652        (crate::Scalar::F32, crate::Scalar::I32) => (
653            crate::Literal::F32(i32::min_float()),
654            crate::Literal::F32(i32::max_float()),
655        ),
656        (crate::Scalar::F32, crate::Scalar::U32) => (
657            crate::Literal::F32(u32::min_float()),
658            crate::Literal::F32(u32::max_float()),
659        ),
660        (crate::Scalar::F32, crate::Scalar::I64) => (
661            crate::Literal::F32(i64::min_float()),
662            crate::Literal::F32(i64::max_float()),
663        ),
664        (crate::Scalar::F32, crate::Scalar::U64) => (
665            crate::Literal::F32(u64::min_float()),
666            crate::Literal::F32(u64::max_float()),
667        ),
668        (crate::Scalar::F64, crate::Scalar::I32) => (
669            crate::Literal::F64(i32::min_float()),
670            crate::Literal::F64(i32::max_float()),
671        ),
672        (crate::Scalar::F64, crate::Scalar::U32) => (
673            crate::Literal::F64(u32::min_float()),
674            crate::Literal::F64(u32::max_float()),
675        ),
676        (crate::Scalar::F64, crate::Scalar::I64) => (
677            crate::Literal::F64(i64::min_float()),
678            crate::Literal::F64(i64::max_float()),
679        ),
680        (crate::Scalar::F64, crate::Scalar::U64) => (
681            crate::Literal::F64(u64::min_float()),
682            crate::Literal::F64(u64::max_float()),
683        ),
684        _ => unreachable!(),
685    }
686}
687
688/// Helper function that returns the string corresponding to the [`VectorSize`](crate::VectorSize)
689pub const fn vector_size_str(size: crate::VectorSize) -> &'static str {
690    match size {
691        crate::VectorSize::Bi => "2",
692        crate::VectorSize::Tri => "3",
693        crate::VectorSize::Quad => "4",
694    }
695}