naga/front/wgsl/lower/
conversion.rs

1//! WGSL's automatic conversions for abstract types.
2
3use crate::front::wgsl::error::{
4    AutoConversionError, AutoConversionLeafScalarError, ConcretizationFailedError,
5};
6use crate::{Handle, Span};
7
8impl<'source, 'temp, 'out> super::ExpressionContext<'source, 'temp, 'out> {
9    /// Try to use WGSL's automatic conversions to convert `expr` to `goal_ty`.
10    ///
11    /// If no conversions are necessary, return `expr` unchanged.
12    ///
13    /// If automatic conversions cannot convert `expr` to `goal_ty`, return an
14    /// [`AutoConversion`] error.
15    ///
16    /// Although the Load Rule is one of the automatic conversions, this
17    /// function assumes it has already been applied if appropriate, as
18    /// indicated by the fact that the Rust type of `expr` is not `Typed<_>`.
19    ///
20    /// [`AutoConversion`]: super::Error::AutoConversion
21    pub fn try_automatic_conversions(
22        &mut self,
23        expr: Handle<crate::Expression>,
24        goal_ty: &crate::proc::TypeResolution,
25        goal_span: Span,
26    ) -> Result<Handle<crate::Expression>, super::Error<'source>> {
27        let expr_span = self.get_expression_span(expr);
28        // Keep the TypeResolution so we can get type names for
29        // structs in error messages.
30        let expr_resolution = super::resolve!(self, expr);
31        let types = &self.module.types;
32        let expr_inner = expr_resolution.inner_with(types);
33        let goal_inner = goal_ty.inner_with(types);
34
35        // If `expr` already has the requested type, we're done.
36        if expr_inner.equivalent(goal_inner, types) {
37            return Ok(expr);
38        }
39
40        let (_expr_scalar, goal_scalar) =
41            match expr_inner.automatically_converts_to(goal_inner, types) {
42                Some(scalars) => scalars,
43                None => {
44                    let gctx = &self.module.to_ctx();
45                    let source_type = expr_resolution.to_wgsl(gctx).into();
46                    let dest_type = goal_ty.to_wgsl(gctx).into();
47
48                    return Err(super::Error::AutoConversion(Box::new(
49                        AutoConversionError {
50                            dest_span: goal_span,
51                            dest_type,
52                            source_span: expr_span,
53                            source_type,
54                        },
55                    )));
56                }
57            };
58
59        self.convert_leaf_scalar(expr, expr_span, goal_scalar)
60    }
61
62    /// Try to convert `expr`'s leaf scalar to `goal` using automatic conversions.
63    ///
64    /// If no conversions are necessary, return `expr` unchanged.
65    ///
66    /// If automatic conversions cannot convert `expr` to `goal_scalar`, return
67    /// an [`AutoConversionLeafScalar`] error.
68    ///
69    /// Although the Load Rule is one of the automatic conversions, this
70    /// function assumes it has already been applied if appropriate, as
71    /// indicated by the fact that the Rust type of `expr` is not `Typed<_>`.
72    ///
73    /// [`AutoConversionLeafScalar`]: super::Error::AutoConversionLeafScalar
74    pub fn try_automatic_conversion_for_leaf_scalar(
75        &mut self,
76        expr: Handle<crate::Expression>,
77        goal_scalar: crate::Scalar,
78        goal_span: Span,
79    ) -> Result<Handle<crate::Expression>, super::Error<'source>> {
80        let expr_span = self.get_expression_span(expr);
81        let expr_resolution = super::resolve!(self, expr);
82        let types = &self.module.types;
83        let expr_inner = expr_resolution.inner_with(types);
84
85        let make_error = || {
86            let gctx = &self.module.to_ctx();
87            let source_type = expr_resolution.to_wgsl(gctx).into();
88            super::Error::AutoConversionLeafScalar(Box::new(AutoConversionLeafScalarError {
89                dest_span: goal_span,
90                dest_scalar: goal_scalar.to_wgsl().into(),
91                source_span: expr_span,
92                source_type,
93            }))
94        };
95
96        let expr_scalar = match expr_inner.scalar() {
97            Some(scalar) => scalar,
98            None => return Err(make_error()),
99        };
100
101        if expr_scalar == goal_scalar {
102            return Ok(expr);
103        }
104
105        if !expr_scalar.automatically_converts_to(goal_scalar) {
106            return Err(make_error());
107        }
108
109        assert!(expr_scalar.is_abstract());
110
111        self.convert_leaf_scalar(expr, expr_span, goal_scalar)
112    }
113
114    fn convert_leaf_scalar(
115        &mut self,
116        expr: Handle<crate::Expression>,
117        expr_span: Span,
118        goal_scalar: crate::Scalar,
119    ) -> Result<Handle<crate::Expression>, super::Error<'source>> {
120        let expr_inner = super::resolve_inner!(self, expr);
121        if let crate::TypeInner::Array { .. } = *expr_inner {
122            self.as_const_evaluator()
123                .cast_array(expr, goal_scalar, expr_span)
124                .map_err(|err| super::Error::ConstantEvaluatorError(err.into(), expr_span))
125        } else {
126            let cast = crate::Expression::As {
127                expr,
128                kind: goal_scalar.kind,
129                convert: Some(goal_scalar.width),
130            };
131            self.append_expression(cast, expr_span)
132        }
133    }
134
135    /// Try to convert `exprs` to `goal_ty` using WGSL's automatic conversions.
136    pub fn try_automatic_conversions_slice(
137        &mut self,
138        exprs: &mut [Handle<crate::Expression>],
139        goal_ty: &crate::proc::TypeResolution,
140        goal_span: Span,
141    ) -> Result<(), super::Error<'source>> {
142        for expr in exprs.iter_mut() {
143            *expr = self.try_automatic_conversions(*expr, goal_ty, goal_span)?;
144        }
145
146        Ok(())
147    }
148
149    /// Apply WGSL's automatic conversions to a vector constructor's arguments.
150    ///
151    /// When calling a vector constructor like `vec3<f32>(...)`, the parameters
152    /// can be a mix of scalars and vectors, with the latter being spread out to
153    /// contribute each of their components as a component of the new value.
154    /// When the element type is explicit, as with `<f32>` in the example above,
155    /// WGSL's automatic conversions should convert abstract scalar and vector
156    /// parameters to the constructor's required scalar type.
157    pub fn try_automatic_conversions_for_vector(
158        &mut self,
159        exprs: &mut [Handle<crate::Expression>],
160        goal_scalar: crate::Scalar,
161        goal_span: Span,
162    ) -> Result<(), super::Error<'source>> {
163        use crate::proc::TypeResolution as Tr;
164        use crate::TypeInner as Ti;
165        let goal_scalar_res = Tr::Value(Ti::Scalar(goal_scalar));
166
167        for (i, expr) in exprs.iter_mut().enumerate() {
168            // Keep the TypeResolution so we can get full type names
169            // in error messages.
170            let expr_resolution = super::resolve!(self, *expr);
171            let types = &self.module.types;
172            let expr_inner = expr_resolution.inner_with(types);
173
174            match *expr_inner {
175                Ti::Scalar(_) => {
176                    *expr = self.try_automatic_conversions(*expr, &goal_scalar_res, goal_span)?;
177                }
178                Ti::Vector { size, scalar: _ } => {
179                    let goal_vector_res = Tr::Value(Ti::Vector {
180                        size,
181                        scalar: goal_scalar,
182                    });
183                    *expr = self.try_automatic_conversions(*expr, &goal_vector_res, goal_span)?;
184                }
185                _ => {
186                    let span = self.get_expression_span(*expr);
187                    return Err(super::Error::InvalidConstructorComponentType(
188                        span, i as i32,
189                    ));
190                }
191            }
192        }
193
194        Ok(())
195    }
196
197    /// Convert `expr` to the leaf scalar type `scalar`.
198    pub fn convert_to_leaf_scalar(
199        &mut self,
200        expr: &mut Handle<crate::Expression>,
201        goal: crate::Scalar,
202    ) -> Result<(), super::Error<'source>> {
203        let inner = super::resolve_inner!(self, *expr);
204        // Do nothing if `inner` doesn't even have leaf scalars;
205        // it's a type error that validation will catch.
206        if inner.scalar() != Some(goal) {
207            let cast = crate::Expression::As {
208                expr: *expr,
209                kind: goal.kind,
210                convert: Some(goal.width),
211            };
212            let expr_span = self.get_expression_span(*expr);
213            *expr = self.append_expression(cast, expr_span)?;
214        }
215
216        Ok(())
217    }
218
219    /// Convert all expressions in `exprs` to a common scalar type.
220    ///
221    /// Note that the caller is responsible for making sure these
222    /// conversions are actually justified. This function simply
223    /// generates `As` expressions, regardless of whether they are
224    /// permitted WGSL automatic conversions. Callers intending to
225    /// implement automatic conversions need to determine for
226    /// themselves whether the casts we we generate are justified,
227    /// perhaps by calling `TypeInner::automatically_converts_to` or
228    /// `Scalar::automatic_conversion_combine`.
229    pub fn convert_slice_to_common_leaf_scalar(
230        &mut self,
231        exprs: &mut [Handle<crate::Expression>],
232        goal: crate::Scalar,
233    ) -> Result<(), super::Error<'source>> {
234        for expr in exprs.iter_mut() {
235            self.convert_to_leaf_scalar(expr, goal)?;
236        }
237
238        Ok(())
239    }
240
241    /// Return an expression for the concretized value of `expr`.
242    ///
243    /// If `expr` is already concrete, return it unchanged.
244    pub fn concretize(
245        &mut self,
246        mut expr: Handle<crate::Expression>,
247    ) -> Result<Handle<crate::Expression>, super::Error<'source>> {
248        let inner = super::resolve_inner!(self, expr);
249        if let Some(scalar) = inner.automatically_convertible_scalar(&self.module.types) {
250            let concretized = scalar.concretize();
251            if concretized != scalar {
252                assert!(scalar.is_abstract());
253                let expr_span = self.get_expression_span(expr);
254                expr = self
255                    .as_const_evaluator()
256                    .cast_array(expr, concretized, expr_span)
257                    .map_err(|err| {
258                        // A `TypeResolution` includes the type's full name, if
259                        // it has one. Also, avoid holding the borrow of `inner`
260                        // across the call to `cast_array`.
261                        let expr_type = &self.typifier()[expr];
262                        super::Error::ConcretizationFailed(Box::new(ConcretizationFailedError {
263                            expr_span,
264                            expr_type: expr_type.to_wgsl(&self.module.to_ctx()).into(),
265                            scalar: concretized.to_wgsl().into(),
266                            inner: err,
267                        }))
268                    })?;
269            }
270        }
271
272        Ok(expr)
273    }
274
275    /// Find the consensus scalar of `components` under WGSL's automatic
276    /// conversions.
277    ///
278    /// If `components` can all be converted to any common scalar via
279    /// WGSL's automatic conversions, return the best such scalar.
280    ///
281    /// The `components` slice must not be empty. All elements' types must
282    /// have been resolved.
283    ///
284    /// If `components` are definitely not acceptable as arguments to such
285    /// constructors, return `Err(i)`, where `i` is the index in
286    /// `components` of some problematic argument.
287    ///
288    /// This function doesn't fully type-check the arguments - it only
289    /// considers their leaf scalar types. This means it may return `Ok`
290    /// even when the Naga validator will reject the resulting
291    /// construction expression later.
292    pub fn automatic_conversion_consensus<'handle, I>(
293        &self,
294        components: I,
295    ) -> Result<crate::Scalar, usize>
296    where
297        I: IntoIterator<Item = &'handle Handle<crate::Expression>>,
298        I::IntoIter: Clone, // for debugging
299    {
300        let types = &self.module.types;
301        let mut inners = components
302            .into_iter()
303            .map(|&c| self.typifier()[c].inner_with(types));
304        log::debug!(
305            "wgsl automatic_conversion_consensus: {:?}",
306            inners
307                .clone()
308                .map(|inner| inner.to_wgsl(&self.module.to_ctx()))
309                .collect::<Vec<String>>()
310        );
311        let mut best = inners.next().unwrap().scalar().ok_or(0_usize)?;
312        for (inner, i) in inners.zip(1..) {
313            let scalar = inner.scalar().ok_or(i)?;
314            match best.automatic_conversion_combine(scalar) {
315                Some(new_best) => {
316                    best = new_best;
317                }
318                None => return Err(i),
319            }
320        }
321
322        log::debug!("    consensus: {:?}", best.to_wgsl());
323        Ok(best)
324    }
325}
326
327impl crate::TypeInner {
328    /// Determine whether `self` automatically converts to `goal`.
329    ///
330    /// If WGSL's automatic conversions (excluding the Load Rule) will
331    /// convert `self` to `goal`, then return a pair `(from, to)`,
332    /// where `from` and `to` are the scalar types of the leaf values
333    /// of `self` and `goal`.
334    ///
335    /// This function assumes that `self` and `goal` are different
336    /// types. Callers should first check whether any conversion is
337    /// needed at all.
338    ///
339    /// If the automatic conversions cannot convert `self` to `goal`,
340    /// return `None`.
341    fn automatically_converts_to(
342        &self,
343        goal: &Self,
344        types: &crate::UniqueArena<crate::Type>,
345    ) -> Option<(crate::Scalar, crate::Scalar)> {
346        use crate::ScalarKind as Sk;
347        use crate::TypeInner as Ti;
348
349        // Automatic conversions only change the scalar type of a value's leaves
350        // (e.g., `vec4<AbstractFloat>` to `vec4<f32>`), never the type
351        // constructors applied to those scalar types (e.g., never scalar to
352        // `vec4`, or `vec2` to `vec3`). So first we check that the type
353        // constructors match, extracting the leaf scalar types in the process.
354        let expr_scalar;
355        let goal_scalar;
356        match (self, goal) {
357            (&Ti::Scalar(expr), &Ti::Scalar(goal)) => {
358                expr_scalar = expr;
359                goal_scalar = goal;
360            }
361            (
362                &Ti::Vector {
363                    size: expr_size,
364                    scalar: expr,
365                },
366                &Ti::Vector {
367                    size: goal_size,
368                    scalar: goal,
369                },
370            ) if expr_size == goal_size => {
371                expr_scalar = expr;
372                goal_scalar = goal;
373            }
374            (
375                &Ti::Matrix {
376                    rows: expr_rows,
377                    columns: expr_columns,
378                    scalar: expr,
379                },
380                &Ti::Matrix {
381                    rows: goal_rows,
382                    columns: goal_columns,
383                    scalar: goal,
384                },
385            ) if expr_rows == goal_rows && expr_columns == goal_columns => {
386                expr_scalar = expr;
387                goal_scalar = goal;
388            }
389            (
390                &Ti::Array {
391                    base: expr_base,
392                    size: expr_size,
393                    stride: _,
394                },
395                &Ti::Array {
396                    base: goal_base,
397                    size: goal_size,
398                    stride: _,
399                },
400            ) if expr_size == goal_size => {
401                return types[expr_base]
402                    .inner
403                    .automatically_converts_to(&types[goal_base].inner, types);
404            }
405            _ => return None,
406        }
407
408        match (expr_scalar.kind, goal_scalar.kind) {
409            (Sk::AbstractFloat, Sk::Float) => {}
410            (Sk::AbstractInt, Sk::Sint | Sk::Uint | Sk::AbstractFloat | Sk::Float) => {}
411            _ => return None,
412        }
413
414        log::trace!("      okay: expr {expr_scalar:?}, goal {goal_scalar:?}");
415        Some((expr_scalar, goal_scalar))
416    }
417
418    fn automatically_convertible_scalar(
419        &self,
420        types: &crate::UniqueArena<crate::Type>,
421    ) -> Option<crate::Scalar> {
422        use crate::TypeInner as Ti;
423        match *self {
424            Ti::Scalar(scalar) | Ti::Vector { scalar, .. } | Ti::Matrix { scalar, .. } => {
425                Some(scalar)
426            }
427            Ti::Array { base, .. } => types[base].inner.automatically_convertible_scalar(types),
428            Ti::Atomic(_)
429            | Ti::Pointer { .. }
430            | Ti::ValuePointer { .. }
431            | Ti::Struct { .. }
432            | Ti::Image { .. }
433            | Ti::Sampler { .. }
434            | Ti::AccelerationStructure
435            | Ti::RayQuery
436            | Ti::BindingArray { .. } => None,
437        }
438    }
439}
440
441impl crate::Scalar {
442    /// Find the common type of `self` and `other` under WGSL's
443    /// automatic conversions.
444    ///
445    /// If there are any scalars to which WGSL's automatic conversions
446    /// will convert both `self` and `other`, return the best such
447    /// scalar. Otherwise, return `None`.
448    pub const fn automatic_conversion_combine(self, other: Self) -> Option<crate::Scalar> {
449        use crate::ScalarKind as Sk;
450
451        match (self.kind, other.kind) {
452            // When the kinds match...
453            (Sk::AbstractFloat, Sk::AbstractFloat)
454            | (Sk::AbstractInt, Sk::AbstractInt)
455            | (Sk::Sint, Sk::Sint)
456            | (Sk::Uint, Sk::Uint)
457            | (Sk::Float, Sk::Float)
458            | (Sk::Bool, Sk::Bool) => {
459                if self.width == other.width {
460                    // ... either no conversion is necessary ...
461                    Some(self)
462                } else {
463                    // ... or no conversion is possible.
464                    // We never convert concrete to concrete, and
465                    // abstract types should have only one size.
466                    None
467                }
468            }
469
470            // AbstractInt converts to AbstractFloat.
471            (Sk::AbstractFloat, Sk::AbstractInt) => Some(self),
472            (Sk::AbstractInt, Sk::AbstractFloat) => Some(other),
473
474            // AbstractFloat converts to Float.
475            (Sk::AbstractFloat, Sk::Float) => Some(other),
476            (Sk::Float, Sk::AbstractFloat) => Some(self),
477
478            // AbstractInt converts to concrete integer or float.
479            (Sk::AbstractInt, Sk::Uint | Sk::Sint | Sk::Float) => Some(other),
480            (Sk::Uint | Sk::Sint | Sk::Float, Sk::AbstractInt) => Some(self),
481
482            // AbstractFloat can't be reconciled with concrete integer types.
483            (Sk::AbstractFloat, Sk::Uint | Sk::Sint) | (Sk::Uint | Sk::Sint, Sk::AbstractFloat) => {
484                None
485            }
486
487            // Nothing can be reconciled with `bool`.
488            (Sk::Bool, _) | (_, Sk::Bool) => None,
489
490            // Different concrete types cannot be reconciled.
491            (Sk::Sint | Sk::Uint | Sk::Float, Sk::Sint | Sk::Uint | Sk::Float) => None,
492        }
493    }
494
495    /// Return `true` if automatic conversions will covert `self` to `goal`.
496    pub fn automatically_converts_to(self, goal: Self) -> bool {
497        self.automatic_conversion_combine(goal) == Some(goal)
498    }
499
500    const fn concretize(self) -> Self {
501        use crate::ScalarKind as Sk;
502        match self.kind {
503            Sk::Sint | Sk::Uint | Sk::Float | Sk::Bool => self,
504            Sk::AbstractInt => Self::I32,
505            Sk::AbstractFloat => Self::F32,
506        }
507    }
508}