Skip to main content

naga/front/wgsl/lower/
construction.rs

1use alloc::{boxed::Box, vec, vec::Vec};
2use core::num::NonZeroU32;
3
4use crate::common::wgsl::{TryToWgsl, TypeContext};
5use crate::front::wgsl::lower::{ExpressionContext, Lowerer};
6use crate::front::wgsl::parse::ast;
7use crate::front::wgsl::{Error, Result};
8use crate::{Handle, Span};
9
10/// A [`constructor built-in function`].
11///
12/// WGSL has two types of such functions:
13///
14/// - Those that fully specify the type being constructed, like
15///   `vec3<f32>(x,y,z)`, which obviously constructs a `vec3<f32>`.
16///
17/// - Those that leave the component type of the composite being constructed
18///   implicit, to be inferred from the argument types, like `vec3(x,y,z)`,
19///   which constructs a `vec3<T>` where `T` is the type of `x`, `y`, and `z`.
20///
21/// This enum represents both cases. The `PartialFoo` variants
22/// represent the second case, where the component type is implicit.
23///
24/// [`constructor built-in function`]: https://gpuweb.github.io/gpuweb/wgsl/#constructor-builtin-function
25pub enum Constructor<T> {
26    /// A vector construction whose component type is inferred from the
27    /// argument: `vec3(1.0)`.
28    PartialVector { size: crate::VectorSize },
29
30    /// A matrix construction whose component type is inferred from the
31    /// argument: `mat2x2(1,2,3,4)`.
32    PartialMatrix {
33        columns: crate::VectorSize,
34        rows: crate::VectorSize,
35    },
36
37    /// An array whose component type and size are inferred from the arguments:
38    /// `array(3,4,5)`.
39    PartialArray,
40
41    /// A known Naga type.
42    ///
43    /// When we match on this type, we need to see the `TypeInner` here, but at
44    /// the point that we build this value we'll still need mutable access to
45    /// the module later. To avoid borrowing from the module, the type parameter
46    /// `T` is `Handle<Type>` initially. Then we use `borrow_inner` to produce a
47    /// version holding a tuple `(Handle<Type>, &TypeInner)`.
48    Type(T),
49}
50
51impl Constructor<Handle<crate::Type>> {
52    /// Return an equivalent `Constructor` value that includes borrowed
53    /// `TypeInner` values alongside any type handles.
54    ///
55    /// The returned form is more convenient to match on, since the patterns
56    /// can actually see what the handle refers to.
57    fn borrow_inner(
58        self,
59        module: &crate::Module,
60    ) -> Constructor<(Handle<crate::Type>, &crate::TypeInner)> {
61        match self {
62            Constructor::PartialVector { size } => Constructor::PartialVector { size },
63            Constructor::PartialMatrix { columns, rows } => {
64                Constructor::PartialMatrix { columns, rows }
65            }
66            Constructor::PartialArray => Constructor::PartialArray,
67            Constructor::Type(handle) => Constructor::Type((handle, &module.types[handle].inner)),
68        }
69    }
70}
71
72enum Components<'a> {
73    None,
74    One {
75        component: Handle<crate::Expression>,
76        span: Span,
77        ty_inner: &'a crate::TypeInner,
78    },
79    Many {
80        components: Vec<Handle<crate::Expression>>,
81        spans: Vec<Span>,
82    },
83}
84
85impl Components<'_> {
86    fn into_components_vec(self) -> Vec<Handle<crate::Expression>> {
87        match self {
88            Self::None => vec![],
89            Self::One { component, .. } => vec![component],
90            Self::Many { components, .. } => components,
91        }
92    }
93}
94
95impl<'source> Lowerer<'source, '_> {
96    /// Generate Naga IR for a type constructor expression.
97    ///
98    /// The `constructor` value represents the head of the constructor
99    /// expression, which is at least a hint of which type is being built; if
100    /// it's one of the `Partial` variants, we need to consider the argument
101    /// types as well.
102    ///
103    /// This is used for [`Call`] expressions, once we've determined that
104    /// the "callable" (in WGSL spec terms) is actually a type.
105    ///
106    /// [`Call`]: ast::Expression::Call
107    pub fn construct(
108        &mut self,
109        span: Span,
110        constructor: Constructor<Handle<crate::Type>>,
111        ty_span: Span,
112        components: &[Handle<ast::Expression<'source>>],
113        ctx: &mut ExpressionContext<'source, '_, '_>,
114    ) -> Result<'source, Handle<crate::Expression>> {
115        use crate::proc::TypeResolution as Tr;
116
117        let components = match *components {
118            [] => Components::None,
119            [component] => {
120                let span = ctx.ast_expressions.get_span(component);
121                let component = self.expression_for_abstract(component, ctx)?;
122                let ty_inner = super::resolve_inner!(ctx, component);
123
124                Components::One {
125                    component,
126                    span,
127                    ty_inner,
128                }
129            }
130            ref ast_components @ [_, _, ..] => {
131                let components = ast_components
132                    .iter()
133                    .map(|&expr| self.expression_for_abstract(expr, ctx))
134                    .collect::<Result<_>>()?;
135                let spans = ast_components
136                    .iter()
137                    .map(|&expr| ctx.ast_expressions.get_span(expr))
138                    .collect();
139
140                for &component in &components {
141                    ctx.grow_types(component)?;
142                }
143
144                Components::Many { components, spans }
145            }
146        };
147
148        // Even though we computed `constructor` above, wait until now to borrow
149        // a reference to the `TypeInner`, so that the component-handling code
150        // above can have mutable access to the type arena.
151        let constructor = constructor.borrow_inner(ctx.module);
152
153        let expr;
154        match (components, constructor) {
155            // Zero-value constructor with explicit type.
156            (Components::None, Constructor::Type((result_ty, inner)))
157                if inner.is_constructible(&ctx.module.types) =>
158            {
159                expr = crate::Expression::ZeroValue(result_ty);
160            }
161            // Zero-value constructor, vector with type inference
162            (Components::None, Constructor::PartialVector { size }) => {
163                // vec2(), vec3(), vec4() return vectors of abstractInts; the same
164                // is not true of the similar constructors for matrices or arrays.
165                // See https://www.w3.org/TR/WGSL/#vec2-builtin et seq.
166                let result_ty = ctx.module.types.insert(
167                    crate::Type {
168                        name: None,
169                        inner: crate::TypeInner::Vector {
170                            size,
171                            scalar: crate::Scalar::ABSTRACT_INT,
172                        },
173                    },
174                    span,
175                );
176                expr = crate::Expression::ZeroValue(result_ty);
177            }
178            // Zero-value constructor, matrix or array with type inference
179            (Components::None, Constructor::PartialMatrix { .. } | Constructor::PartialArray) => {
180                // We have no arguments from which to infer the result type, so
181                // partial constructors aren't acceptable here.
182                return Err(Box::new(Error::TypeNotInferable(ty_span)));
183            }
184
185            // Scalar constructor & conversion (scalar -> scalar)
186            (
187                Components::One {
188                    component,
189                    ty_inner: &crate::TypeInner::Scalar { .. },
190                    ..
191                },
192                Constructor::Type((_, &crate::TypeInner::Scalar(scalar))),
193            ) => {
194                expr = crate::Expression::As {
195                    expr: component,
196                    target: scalar,
197                };
198            }
199
200            // Vector conversion (vector -> vector)
201            (
202                Components::One {
203                    component,
204                    ty_inner: &crate::TypeInner::Vector { size: src_size, .. },
205                    ..
206                },
207                Constructor::Type((
208                    _,
209                    &crate::TypeInner::Vector {
210                        size: dst_size,
211                        scalar: dst_scalar,
212                    },
213                )),
214            ) if dst_size == src_size => {
215                expr = crate::Expression::As {
216                    expr: component,
217                    target: dst_scalar,
218                };
219            }
220
221            // Vector conversion (vector -> vector) - partial
222            (
223                Components::One {
224                    component,
225                    ty_inner: &crate::TypeInner::Vector { size: src_size, .. },
226                    ..
227                },
228                Constructor::PartialVector { size: dst_size },
229            ) if dst_size == src_size => {
230                // This is a trivial conversion: the sizes match, and a Partial
231                // constructor doesn't specify a scalar type, so nothing can
232                // possibly happen.
233                return Ok(component);
234            }
235
236            // Matrix conversion (matrix -> matrix)
237            (
238                Components::One {
239                    component,
240                    ty_inner:
241                        &crate::TypeInner::Matrix {
242                            columns: src_columns,
243                            rows: src_rows,
244                            ..
245                        },
246                    ..
247                },
248                Constructor::Type((
249                    _,
250                    &crate::TypeInner::Matrix {
251                        columns: dst_columns,
252                        rows: dst_rows,
253                        scalar: dst_scalar,
254                    },
255                )),
256            ) if dst_columns == src_columns && dst_rows == src_rows => {
257                expr = crate::Expression::As {
258                    expr: component,
259                    target: dst_scalar,
260                };
261            }
262
263            // Matrix conversion (matrix -> matrix) - partial
264            (
265                Components::One {
266                    component,
267                    ty_inner:
268                        &crate::TypeInner::Matrix {
269                            columns: src_columns,
270                            rows: src_rows,
271                            ..
272                        },
273                    ..
274                },
275                Constructor::PartialMatrix {
276                    columns: dst_columns,
277                    rows: dst_rows,
278                },
279            ) if dst_columns == src_columns && dst_rows == src_rows => {
280                // This is a trivial conversion: the sizes match, and a Partial
281                // constructor doesn't specify a scalar type, so nothing can
282                // possibly happen.
283                return Ok(component);
284            }
285
286            // Vector constructor (splat) - infer type
287            (
288                Components::One {
289                    component,
290                    ty_inner: &crate::TypeInner::Scalar { .. },
291                    ..
292                },
293                Constructor::PartialVector { size },
294            ) => {
295                expr = crate::Expression::Splat {
296                    size,
297                    value: component,
298                };
299            }
300
301            // Vector constructor (splat)
302            (
303                Components::One {
304                    mut component,
305                    ty_inner: &crate::TypeInner::Scalar(component_scalar),
306                    span,
307                },
308                Constructor::Type((
309                    type_handle,
310                    &crate::TypeInner::Vector {
311                        size,
312                        scalar: vec_scalar,
313                    },
314                )),
315            ) => {
316                // Splat only allows automatic conversions of the component's scalar.
317                if !component_scalar.automatically_converts_to(vec_scalar) {
318                    let component_ty = &ctx.typifier()[component];
319                    let arg_ty = ctx.type_resolution_to_string(component_ty);
320                    return Err(Box::new(Error::WrongArgumentType {
321                        function: ctx.type_to_string(type_handle),
322                        call_span: ty_span,
323                        arg_span: span,
324                        arg_index: 0,
325                        arg_ty,
326                        allowed: vec![vec_scalar.to_wgsl_for_diagnostics()],
327                    }));
328                }
329                ctx.convert_slice_to_common_leaf_scalar(
330                    core::slice::from_mut(&mut component),
331                    vec_scalar,
332                )?;
333                expr = crate::Expression::Splat {
334                    size,
335                    value: component,
336                };
337            }
338
339            // Vector constructor (by elements), partial
340            (
341                Components::Many {
342                    mut components,
343                    spans,
344                },
345                Constructor::PartialVector { size },
346            ) => {
347                let consensus_scalar = ctx
348                    .automatic_conversion_consensus(None, &components)
349                    .map_err(|index| {
350                        Error::InvalidConstructorComponentType(spans[index], index as i32)
351                    })?;
352                ctx.convert_slice_to_common_leaf_scalar(&mut components, consensus_scalar)?;
353                let inner = consensus_scalar.to_inner_vector(size);
354                let ty = ctx.ensure_type_exists(inner);
355                expr = crate::Expression::Compose { ty, components };
356            }
357
358            // Vector constructor (by elements), full type given
359            (
360                Components::Many { mut components, .. },
361                Constructor::Type((ty, &crate::TypeInner::Vector { scalar, .. })),
362            ) => {
363                ctx.try_automatic_conversions_for_vector(&mut components, scalar, ty_span)?;
364                expr = crate::Expression::Compose { ty, components };
365            }
366
367            // Matrix constructor (by elements), partial
368            (
369                Components::Many {
370                    mut components,
371                    spans,
372                },
373                Constructor::PartialMatrix { columns, rows },
374            ) if components.len() == columns as usize * rows as usize => {
375                let consensus_scalar = ctx
376                    .automatic_conversion_consensus(
377                        Some(crate::Scalar::ABSTRACT_FLOAT),
378                        &components,
379                    )
380                    .map_err(|index| {
381                        Error::InvalidConstructorComponentType(spans[index], index as i32)
382                    })?;
383                ctx.convert_slice_to_common_leaf_scalar(&mut components, consensus_scalar)?;
384                let vec_ty = ctx.ensure_type_exists(consensus_scalar.to_inner_vector(rows));
385
386                let components = components
387                    .chunks(rows as usize)
388                    .map(|vec_components| {
389                        ctx.append_expression(
390                            crate::Expression::Compose {
391                                ty: vec_ty,
392                                components: Vec::from(vec_components),
393                            },
394                            Default::default(),
395                        )
396                    })
397                    .collect::<Result<Vec<_>>>()?;
398
399                let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix {
400                    columns,
401                    rows,
402                    scalar: consensus_scalar,
403                });
404                expr = crate::Expression::Compose { ty, components };
405            }
406
407            // Matrix constructor (by elements), type given
408            (
409                Components::Many { mut components, .. },
410                Constructor::Type((
411                    _,
412                    &crate::TypeInner::Matrix {
413                        columns,
414                        rows,
415                        scalar,
416                    },
417                )),
418            ) if components.len() == columns as usize * rows as usize => {
419                let element = Tr::Value(crate::TypeInner::Scalar(scalar));
420                ctx.try_automatic_conversions_slice(&mut components, &element, ty_span)?;
421                let vec_ty = ctx.ensure_type_exists(scalar.to_inner_vector(rows));
422
423                let components = components
424                    .chunks(rows as usize)
425                    .map(|vec_components| {
426                        ctx.append_expression(
427                            crate::Expression::Compose {
428                                ty: vec_ty,
429                                components: Vec::from(vec_components),
430                            },
431                            Default::default(),
432                        )
433                    })
434                    .collect::<Result<Vec<_>>>()?;
435
436                let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix {
437                    columns,
438                    rows,
439                    scalar,
440                });
441                expr = crate::Expression::Compose { ty, components };
442            }
443
444            // Matrix constructor (by columns), partial
445            (
446                Components::Many {
447                    mut components,
448                    spans,
449                },
450                Constructor::PartialMatrix { columns, rows },
451            ) if components.len() == columns as usize => {
452                let consensus_scalar = ctx
453                    .automatic_conversion_consensus(
454                        Some(crate::Scalar::ABSTRACT_FLOAT),
455                        &components,
456                    )
457                    .map_err(|index| {
458                        Error::InvalidConstructorComponentType(spans[index], index as i32)
459                    })?;
460
461                let component_ty = crate::TypeInner::Vector {
462                    size: rows,
463                    scalar: consensus_scalar,
464                };
465                ctx.try_automatic_conversions_slice(
466                    &mut components,
467                    &Tr::Value(component_ty),
468                    ty_span,
469                )?;
470
471                let ty = ctx.ensure_type_exists(crate::TypeInner::Matrix {
472                    columns,
473                    rows,
474                    scalar: consensus_scalar,
475                });
476                expr = crate::Expression::Compose { ty, components };
477            }
478
479            // Matrix constructor (by columns), type given
480            (
481                Components::Many { mut components, .. },
482                Constructor::Type((
483                    ty,
484                    &crate::TypeInner::Matrix {
485                        columns,
486                        rows,
487                        scalar,
488                    },
489                )),
490            ) if components.len() == columns as usize => {
491                let component_ty = crate::TypeInner::Vector { size: rows, scalar };
492                ctx.try_automatic_conversions_slice(
493                    &mut components,
494                    &Tr::Value(component_ty),
495                    ty_span,
496                )?;
497                expr = crate::Expression::Compose { ty, components };
498            }
499
500            // Array constructor - infer type
501            (components, Constructor::PartialArray) => {
502                let mut components = components.into_components_vec();
503                if let Ok(consensus_scalar) = ctx.automatic_conversion_consensus(None, &components)
504                {
505                    // Note that this will *not* necessarily convert all the
506                    // components to the same type! The `automatic_conversion_consensus`
507                    // method only considers the parameters' leaf scalar
508                    // types; the parameters themselves could be any mix of
509                    // vectors, matrices, and scalars.
510                    //
511                    // But *if* it is possible for this array construction
512                    // expression to be well-typed at all, then all the
513                    // parameters must have the same type constructors (vec,
514                    // matrix, scalar) applied to their leaf scalars, so
515                    // reconciling their scalars is always the right thing to
516                    // do. And if this array construction is not well-typed,
517                    // these conversions will not make it so, and we can let
518                    // validation catch the error.
519                    ctx.convert_slice_to_common_leaf_scalar(&mut components, consensus_scalar)?;
520                } else {
521                    // There's no consensus scalar. Emit the `Compose`
522                    // expression anyway, and let validation catch the problem.
523                }
524
525                let base = ctx.register_type(components[0])?;
526
527                ctx.layouter.update(ctx.module.to_ctx()).map_err(|err| {
528                    let crate::proc::LayoutErrorInner::TooLarge = err.inner else {
529                        unreachable!("unexpected layout error: {err:?}");
530                    };
531                    // This error could be for any type that was pending layout. Lots of
532                    // type definitions don't get spans, so the error message may not be
533                    // very useful.
534                    Box::new(Error::TypeTooLarge {
535                        span: ctx.module.types.get_span(err.ty),
536                    })
537                })?;
538                let stride = ctx.layouter[base].to_stride();
539
540                let inner = crate::TypeInner::Array {
541                    base,
542                    size: crate::ArraySize::Constant(
543                        NonZeroU32::new(u32::try_from(components.len()).unwrap()).unwrap(),
544                    ),
545                    stride,
546                };
547                let ty = ctx.ensure_type_exists(inner);
548
549                expr = crate::Expression::Compose { ty, components };
550            }
551
552            // Array constructor, explicit type.
553            (
554                components,
555                Constructor::Type((ty, inner @ &crate::TypeInner::Array { base, .. })),
556            ) if inner.is_constructible(&ctx.module.types) => {
557                let mut components = components.into_components_vec();
558                ctx.try_automatic_conversions_slice(&mut components, &Tr::Handle(base), ty_span)?;
559                expr = crate::Expression::Compose { ty, components };
560            }
561
562            // Struct constructor
563            (
564                components,
565                Constructor::Type((ty, inner @ &crate::TypeInner::Struct { ref members, .. })),
566            ) if inner.is_constructible(&ctx.module.types) => {
567                let mut components = components.into_components_vec();
568                let struct_ty_span = ctx.module.types.get_span(ty);
569
570                // Make a vector of the members' type handles in advance, to
571                // avoid borrowing `members` from `ctx` while we generate
572                // new code.
573                let members: Vec<Handle<crate::Type>> = members.iter().map(|m| m.ty).collect();
574
575                for (component, &ty) in components.iter_mut().zip(&members) {
576                    *component =
577                        ctx.try_automatic_conversions(*component, &Tr::Handle(ty), struct_ty_span)?;
578                }
579                expr = crate::Expression::Compose { ty, components };
580            }
581
582            // ERRORS
583
584            // Bad conversion (type cast)
585            (
586                Components::One {
587                    span, component, ..
588                },
589                Constructor::Type((
590                    ty,
591                    &(crate::TypeInner::Scalar { .. }
592                    | crate::TypeInner::Vector { .. }
593                    | crate::TypeInner::Matrix { .. }),
594                )),
595            ) => {
596                let component_ty = &ctx.typifier()[component];
597                let from_type = ctx.type_resolution_to_string(component_ty);
598                return Err(Box::new(Error::BadTypeCast {
599                    span,
600                    from_type,
601                    to_type: ctx.type_to_string(ty),
602                }));
603            }
604
605            // Too many parameters for scalar constructor
606            (
607                Components::Many { spans, .. },
608                Constructor::Type((_, &crate::TypeInner::Scalar { .. })),
609            ) => {
610                let span = spans[1].until(spans.last().unwrap());
611                return Err(Box::new(Error::UnexpectedComponents(span)));
612            }
613
614            // Other types can't be constructed
615            _ => return Err(Box::new(Error::TypeNotConstructible(ty_span))),
616        }
617
618        let expr = ctx.append_expression(expr, span)?;
619        Ok(expr)
620    }
621}