librashader_naga/front/wgsl/lower/
conversion.rs

1//! WGSL's automatic conversions for abstract types.
2
3use crate::{Handle, Span};
4
5impl<'source, 'temp, 'out> super::ExpressionContext<'source, 'temp, 'out> {
6    /// Try to use WGSL's automatic conversions to convert `expr` to `goal_ty`.
7    ///
8    /// If no conversions are necessary, return `expr` unchanged.
9    ///
10    /// If automatic conversions cannot convert `expr` to `goal_ty`, return an
11    /// [`AutoConversion`] error.
12    ///
13    /// Although the Load Rule is one of the automatic conversions, this
14    /// function assumes it has already been applied if appropriate, as
15    /// indicated by the fact that the Rust type of `expr` is not `Typed<_>`.
16    ///
17    /// [`AutoConversion`]: super::Error::AutoConversion
18    pub fn try_automatic_conversions(
19        &mut self,
20        expr: Handle<crate::Expression>,
21        goal_ty: &crate::proc::TypeResolution,
22        goal_span: Span,
23    ) -> Result<Handle<crate::Expression>, super::Error<'source>> {
24        let expr_span = self.get_expression_span(expr);
25        // Keep the TypeResolution so we can get type names for
26        // structs in error messages.
27        let expr_resolution = super::resolve!(self, expr);
28        let types = &self.module.types;
29        let expr_inner = expr_resolution.inner_with(types);
30        let goal_inner = goal_ty.inner_with(types);
31
32        // If `expr` already has the requested type, we're done.
33        if expr_inner.equivalent(goal_inner, types) {
34            return Ok(expr);
35        }
36
37        let (_expr_scalar, goal_scalar) =
38            match expr_inner.automatically_converts_to(goal_inner, types) {
39                Some(scalars) => scalars,
40                None => {
41                    let gctx = &self.module.to_ctx();
42                    let source_type = expr_resolution.to_wgsl(gctx);
43                    let dest_type = goal_ty.to_wgsl(gctx);
44
45                    return Err(super::Error::AutoConversion {
46                        dest_span: goal_span,
47                        dest_type,
48                        source_span: expr_span,
49                        source_type,
50                    });
51                }
52            };
53
54        let converted = if let crate::TypeInner::Array { .. } = *goal_inner {
55            let span = self.get_expression_span(expr);
56            self.as_const_evaluator()
57                .cast_array(expr, goal_scalar, span)
58                .map_err(|err| super::Error::ConstantEvaluatorError(err, span))?
59        } else {
60            let cast = crate::Expression::As {
61                expr,
62                kind: goal_scalar.kind,
63                convert: Some(goal_scalar.width),
64            };
65            self.append_expression(cast, expr_span)?
66        };
67
68        Ok(converted)
69    }
70
71    /// Try to convert `exprs` to `goal_ty` using WGSL's automatic conversions.
72    pub fn try_automatic_conversions_slice(
73        &mut self,
74        exprs: &mut [Handle<crate::Expression>],
75        goal_ty: &crate::proc::TypeResolution,
76        goal_span: Span,
77    ) -> Result<(), super::Error<'source>> {
78        for expr in exprs.iter_mut() {
79            *expr = self.try_automatic_conversions(*expr, goal_ty, goal_span)?;
80        }
81
82        Ok(())
83    }
84
85    /// Apply WGSL's automatic conversions to a vector constructor's arguments.
86    ///
87    /// When calling a vector constructor like `vec3<f32>(...)`, the parameters
88    /// can be a mix of scalars and vectors, with the latter being spread out to
89    /// contribute each of their components as a component of the new value.
90    /// When the element type is explicit, as with `<f32>` in the example above,
91    /// WGSL's automatic conversions should convert abstract scalar and vector
92    /// parameters to the constructor's required scalar type.
93    pub fn try_automatic_conversions_for_vector(
94        &mut self,
95        exprs: &mut [Handle<crate::Expression>],
96        goal_scalar: crate::Scalar,
97        goal_span: Span,
98    ) -> Result<(), super::Error<'source>> {
99        use crate::proc::TypeResolution as Tr;
100        use crate::TypeInner as Ti;
101        let goal_scalar_res = Tr::Value(Ti::Scalar(goal_scalar));
102
103        for (i, expr) in exprs.iter_mut().enumerate() {
104            // Keep the TypeResolution so we can get full type names
105            // in error messages.
106            let expr_resolution = super::resolve!(self, *expr);
107            let types = &self.module.types;
108            let expr_inner = expr_resolution.inner_with(types);
109
110            match *expr_inner {
111                Ti::Scalar(_) => {
112                    *expr = self.try_automatic_conversions(*expr, &goal_scalar_res, goal_span)?;
113                }
114                Ti::Vector { size, scalar: _ } => {
115                    let goal_vector_res = Tr::Value(Ti::Vector {
116                        size,
117                        scalar: goal_scalar,
118                    });
119                    *expr = self.try_automatic_conversions(*expr, &goal_vector_res, goal_span)?;
120                }
121                _ => {
122                    let span = self.get_expression_span(*expr);
123                    return Err(super::Error::InvalidConstructorComponentType(
124                        span, i as i32,
125                    ));
126                }
127            }
128        }
129
130        Ok(())
131    }
132
133    /// Convert all expressions in `exprs` to a common scalar type.
134    ///
135    /// Note that the caller is responsible for making sure these
136    /// conversions are actually justified. This function simply
137    /// generates `As` expressions, regardless of whether they are
138    /// permitted WGSL automatic conversions. Callers intending to
139    /// implement automatic conversions need to determine for
140    /// themselves whether the casts we we generate are justified,
141    /// perhaps by calling `TypeInner::automatically_converts_to` or
142    /// `Scalar::automatic_conversion_combine`.
143    pub fn convert_slice_to_common_scalar(
144        &mut self,
145        exprs: &mut [Handle<crate::Expression>],
146        goal: crate::Scalar,
147    ) -> Result<(), super::Error<'source>> {
148        for expr in exprs.iter_mut() {
149            let inner = super::resolve_inner!(self, *expr);
150            // Do nothing if `inner` doesn't even have leaf scalars;
151            // it's a type error that validation will catch.
152            if inner.scalar() != Some(goal) {
153                let cast = crate::Expression::As {
154                    expr: *expr,
155                    kind: goal.kind,
156                    convert: Some(goal.width),
157                };
158                let expr_span = self.get_expression_span(*expr);
159                *expr = self.append_expression(cast, expr_span)?;
160            }
161        }
162
163        Ok(())
164    }
165
166    /// Return an expression for the concretized value of `expr`.
167    ///
168    /// If `expr` is already concrete, return it unchanged.
169    pub fn concretize(
170        &mut self,
171        mut expr: Handle<crate::Expression>,
172    ) -> Result<Handle<crate::Expression>, super::Error<'source>> {
173        let inner = super::resolve_inner!(self, expr);
174        if let Some(scalar) = inner.automatically_convertible_scalar(&self.module.types) {
175            let concretized = scalar.concretize();
176            if concretized != scalar {
177                let span = self.get_expression_span(expr);
178                expr = self
179                    .as_const_evaluator()
180                    .cast_array(expr, concretized, span)
181                    .map_err(|err| super::Error::ConstantEvaluatorError(err, span))?;
182            }
183        }
184
185        Ok(expr)
186    }
187}
188
189impl crate::TypeInner {
190    /// Determine whether `self` automatically converts to `goal`.
191    ///
192    /// If WGSL's automatic conversions (excluding the Load Rule) will
193    /// convert `self` to `goal`, then return a pair `(from, to)`,
194    /// where `from` and `to` are the scalar types of the leaf values
195    /// of `self` and `goal`.
196    ///
197    /// This function assumes that `self` and `goal` are different
198    /// types. Callers should first check whether any conversion is
199    /// needed at all.
200    ///
201    /// If the automatic conversions cannot convert `self` to `goal`,
202    /// return `None`.
203    fn automatically_converts_to(
204        &self,
205        goal: &Self,
206        types: &crate::UniqueArena<crate::Type>,
207    ) -> Option<(crate::Scalar, crate::Scalar)> {
208        use crate::ScalarKind as Sk;
209        use crate::TypeInner as Ti;
210
211        // Automatic conversions only change the scalar type of a value's leaves
212        // (e.g., `vec4<AbstractFloat>` to `vec4<f32>`), never the type
213        // constructors applied to those scalar types (e.g., never scalar to
214        // `vec4`, or `vec2` to `vec3`). So first we check that the type
215        // constructors match, extracting the leaf scalar types in the process.
216        let expr_scalar;
217        let goal_scalar;
218        match (self, goal) {
219            (&Ti::Scalar(expr), &Ti::Scalar(goal)) => {
220                expr_scalar = expr;
221                goal_scalar = goal;
222            }
223            (
224                &Ti::Vector {
225                    size: expr_size,
226                    scalar: expr,
227                },
228                &Ti::Vector {
229                    size: goal_size,
230                    scalar: goal,
231                },
232            ) if expr_size == goal_size => {
233                expr_scalar = expr;
234                goal_scalar = goal;
235            }
236            (
237                &Ti::Matrix {
238                    rows: expr_rows,
239                    columns: expr_columns,
240                    scalar: expr,
241                },
242                &Ti::Matrix {
243                    rows: goal_rows,
244                    columns: goal_columns,
245                    scalar: goal,
246                },
247            ) if expr_rows == goal_rows && expr_columns == goal_columns => {
248                expr_scalar = expr;
249                goal_scalar = goal;
250            }
251            (
252                &Ti::Array {
253                    base: expr_base,
254                    size: expr_size,
255                    stride: _,
256                },
257                &Ti::Array {
258                    base: goal_base,
259                    size: goal_size,
260                    stride: _,
261                },
262            ) if expr_size == goal_size => {
263                return types[expr_base]
264                    .inner
265                    .automatically_converts_to(&types[goal_base].inner, types);
266            }
267            _ => return None,
268        }
269
270        match (expr_scalar.kind, goal_scalar.kind) {
271            (Sk::AbstractFloat, Sk::Float) => {}
272            (Sk::AbstractInt, Sk::Sint | Sk::Uint | Sk::AbstractFloat | Sk::Float) => {}
273            _ => return None,
274        }
275
276        log::trace!("      okay: expr {expr_scalar:?}, goal {goal_scalar:?}");
277        Some((expr_scalar, goal_scalar))
278    }
279
280    fn automatically_convertible_scalar(
281        &self,
282        types: &crate::UniqueArena<crate::Type>,
283    ) -> Option<crate::Scalar> {
284        use crate::TypeInner as Ti;
285        match *self {
286            Ti::Scalar(scalar) | Ti::Vector { scalar, .. } | Ti::Matrix { scalar, .. } => {
287                Some(scalar)
288            }
289            Ti::Array { base, .. } => types[base].inner.automatically_convertible_scalar(types),
290            Ti::Atomic(_)
291            | Ti::Pointer { .. }
292            | Ti::ValuePointer { .. }
293            | Ti::Struct { .. }
294            | Ti::Image { .. }
295            | Ti::Sampler { .. }
296            | Ti::AccelerationStructure
297            | Ti::RayQuery
298            | Ti::BindingArray { .. } => None,
299        }
300    }
301}
302
303impl crate::Scalar {
304    /// Find the common type of `self` and `other` under WGSL's
305    /// automatic conversions.
306    ///
307    /// If there are any scalars to which WGSL's automatic conversions
308    /// will convert both `self` and `other`, return the best such
309    /// scalar. Otherwise, return `None`.
310    pub const fn automatic_conversion_combine(self, other: Self) -> Option<crate::Scalar> {
311        use crate::ScalarKind as Sk;
312
313        match (self.kind, other.kind) {
314            // When the kinds match...
315            (Sk::AbstractFloat, Sk::AbstractFloat)
316            | (Sk::AbstractInt, Sk::AbstractInt)
317            | (Sk::Sint, Sk::Sint)
318            | (Sk::Uint, Sk::Uint)
319            | (Sk::Float, Sk::Float)
320            | (Sk::Bool, Sk::Bool) => {
321                if self.width == other.width {
322                    // ... either no conversion is necessary ...
323                    Some(self)
324                } else {
325                    // ... or no conversion is possible.
326                    // We never convert concrete to concrete, and
327                    // abstract types should have only one size.
328                    None
329                }
330            }
331
332            // AbstractInt converts to AbstractFloat.
333            (Sk::AbstractFloat, Sk::AbstractInt) => Some(self),
334            (Sk::AbstractInt, Sk::AbstractFloat) => Some(other),
335
336            // AbstractFloat converts to Float.
337            (Sk::AbstractFloat, Sk::Float) => Some(other),
338            (Sk::Float, Sk::AbstractFloat) => Some(self),
339
340            // AbstractInt converts to concrete integer or float.
341            (Sk::AbstractInt, Sk::Uint | Sk::Sint | Sk::Float) => Some(other),
342            (Sk::Uint | Sk::Sint | Sk::Float, Sk::AbstractInt) => Some(self),
343
344            // AbstractFloat can't be reconciled with concrete integer types.
345            (Sk::AbstractFloat, Sk::Uint | Sk::Sint) | (Sk::Uint | Sk::Sint, Sk::AbstractFloat) => {
346                None
347            }
348
349            // Nothing can be reconciled with `bool`.
350            (Sk::Bool, _) | (_, Sk::Bool) => None,
351
352            // Different concrete types cannot be reconciled.
353            (Sk::Sint | Sk::Uint | Sk::Float, Sk::Sint | Sk::Uint | Sk::Float) => None,
354        }
355    }
356
357    const fn concretize(self) -> Self {
358        use crate::ScalarKind as Sk;
359        match self.kind {
360            Sk::Sint | Sk::Uint | Sk::Float | Sk::Bool => self,
361            Sk::AbstractInt => Self::I32,
362            Sk::AbstractFloat => Self::F32,
363        }
364    }
365}