naga/front/wgsl/lower/
conversion.rs

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