Skip to main content

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