naga/valid/
type.rs

1use alloc::string::String;
2
3use super::Capabilities;
4use crate::{arena::Handle, ir, proc::Alignment};
5
6bitflags::bitflags! {
7    /// Flags associated with [`Type`]s by [`Validator`].
8    ///
9    /// [`Type`]: crate::Type
10    /// [`Validator`]: crate::valid::Validator
11    #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
12    #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
13    #[repr(transparent)]
14    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
15    pub struct TypeFlags: u8 {
16        /// Can be used for data variables.
17        ///
18        /// This flag is required on types of local variables, function
19        /// arguments, array elements, and struct members.
20        ///
21        /// This includes all types except [`Image`], [`Sampler`],
22        /// and some [`Pointer`] types.
23        ///
24        /// [`Image`]: crate::TypeInner::Image
25        /// [`Sampler`]: crate::TypeInner::Sampler
26        /// [`Pointer`]: crate::TypeInner::Pointer
27        const DATA = 0x1;
28
29        /// The data type has a size known by pipeline creation time.
30        ///
31        /// Unsized types are quite restricted. The only unsized types permitted
32        /// by Naga, other than the non-[`DATA`] types like [`Image`] and
33        /// [`Sampler`], are dynamically-sized [`Array`]s, and [`Struct`]s whose
34        /// last members are such arrays. See the documentation for those types
35        /// for details.
36        ///
37        /// [`DATA`]: TypeFlags::DATA
38        /// [`Image`]: crate::TypeInner::Image
39        /// [`Sampler`]: crate::TypeInner::Sampler
40        /// [`Array`]: crate::TypeInner::Array
41        /// [`Struct`]: crate::TypeInner::Struct
42        const SIZED = 0x2;
43
44        /// The data can be copied around.
45        const COPY = 0x4;
46
47        /// Can be be used in pipeline stage I/O.
48        ///
49        /// Applies to the following:
50        ///   - Types that may be used in a [`Location`] binding (numeric scalars and vectors)
51        ///   - `@blend_src` structs
52        ///
53        /// See [location-attr] and [input-output].
54        ///
55        /// [`Location`]: crate::Binding::Location
56        /// [location-attr]: https://gpuweb.github.io/gpuweb/wgsl/#location-attr
57        /// [input-output]: https://gpuweb.github.io/gpuweb/wgsl/#input-output-locations
58        /// https://gpuweb.github.io/gpuweb/wgsl/#location-attr
59        const IO_SHAREABLE = 0x8;
60
61        /// Can be used for host-shareable structures.
62        const HOST_SHAREABLE = 0x10;
63
64        /// The set of types with a fixed size at shader-creation time (ie. everything
65        /// except arrays sized by an override-expression)
66        const CREATION_RESOLVED = 0x20;
67
68        /// This type can be passed as a function argument.
69        const ARGUMENT = 0x40;
70
71        /// A WGSL [constructible] type.
72        ///
73        /// The constructible types are scalars, vectors, matrices, fixed-size
74        /// arrays of constructible types, and structs whose members are all
75        /// constructible.
76        ///
77        /// [constructible]: https://gpuweb.github.io/gpuweb/wgsl/#constructible
78        const CONSTRUCTIBLE = 0x80;
79    }
80}
81
82#[derive(Clone, Copy, Debug, thiserror::Error)]
83#[cfg_attr(test, derive(PartialEq))]
84pub enum Disalignment {
85    #[error("The array stride {stride} is not a multiple of the required alignment {alignment}")]
86    ArrayStride { stride: u32, alignment: Alignment },
87    #[error("The struct span {span}, is not a multiple of the required alignment {alignment}")]
88    StructSpan { span: u32, alignment: Alignment },
89    #[error("The struct member[{index}] offset {offset} is not a multiple of the required alignment {alignment}")]
90    MemberOffset {
91        index: u32,
92        offset: u32,
93        alignment: Alignment,
94    },
95    #[error("The struct member[{index}] offset {offset} must be at least {expected}")]
96    MemberOffsetAfterStruct {
97        index: u32,
98        offset: u32,
99        expected: u32,
100    },
101    #[error("The struct member[{index}] is not statically sized")]
102    UnsizedMember { index: u32 },
103    #[error("The type is not host-shareable")]
104    NonHostShareable,
105}
106
107#[derive(Clone, Debug, thiserror::Error)]
108#[cfg_attr(test, derive(PartialEq))]
109pub enum TypeError {
110    #[error("Capability {0:?} is required")]
111    MissingCapability(Capabilities),
112    #[error("The {0:?} scalar width {1} is not supported for an atomic")]
113    InvalidAtomicWidth(crate::ScalarKind, crate::Bytes),
114    #[error("Invalid type for pointer target {0:?}")]
115    InvalidPointerBase(Handle<crate::Type>),
116    #[error("Unsized types like {base:?} must be in the `Storage` address space, not `{space:?}`")]
117    InvalidPointerToUnsized {
118        base: Handle<crate::Type>,
119        space: crate::AddressSpace,
120    },
121    #[error("Expected data type, found {0:?}")]
122    InvalidData(Handle<crate::Type>),
123    #[error("Base type {0:?} for the array is invalid")]
124    InvalidArrayBaseType(Handle<crate::Type>),
125    #[error("Matrix elements must always be floating-point types")]
126    MatrixElementNotFloat,
127    #[error("The constant {0:?} is specialized, and cannot be used as an array size")]
128    UnsupportedSpecializedArrayLength(Handle<crate::Constant>),
129    #[error("{} of dimensionality {dim:?} and class {class:?} are not supported", if *.arrayed {"Arrayed images"} else {"Images"})]
130    UnsupportedImageType {
131        dim: crate::ImageDimension,
132        arrayed: bool,
133        class: crate::ImageClass,
134    },
135    #[error("Array stride {stride} does not match the expected {expected}")]
136    InvalidArrayStride { stride: u32, expected: u32 },
137    #[error("Field '{0}' can't be dynamically-sized, has type {1:?}")]
138    InvalidDynamicArray(String, Handle<crate::Type>),
139    #[error("The base handle {0:?} has to be a struct")]
140    BindingArrayBaseTypeNotStruct(Handle<crate::Type>),
141    #[error("Binding arrays of external textures are not yet supported")]
142    BindingArrayBaseExternalTextures,
143    #[error("Structure member[{index}] at {offset} overlaps the previous member")]
144    MemberOverlap { index: u32, offset: u32 },
145    #[error(
146        "Structure member[{index}] at {offset} and size {size} crosses the structure boundary of size {span}"
147    )]
148    MemberOutOfBounds {
149        index: u32,
150        offset: u32,
151        size: u32,
152        span: u32,
153    },
154    #[error("Structure types must have at least one member")]
155    EmptyStruct,
156    #[error("Invalid `@blend_src` structure: {0}")]
157    InvalidBlendSrc(super::VaryingError),
158    #[error(transparent)]
159    WidthError(#[from] WidthError),
160    #[error(
161        "The base handle {0:?} has an override-expression that didn't get resolved to a constant"
162    )]
163    UnresolvedOverride(Handle<crate::Type>),
164    #[error("Override-sized array type {0:?} does not have a positive size")]
165    InvalidArraySize(Handle<crate::Type>),
166}
167
168#[derive(Clone, Debug, thiserror::Error)]
169#[cfg_attr(test, derive(PartialEq))]
170pub enum WidthError {
171    #[error("The {0:?} scalar width {1} is not supported")]
172    Invalid(crate::ScalarKind, crate::Bytes),
173    #[error("Using `{name}` values requires the `naga::valid::Capabilities::{flag}` flag")]
174    MissingCapability {
175        name: &'static str,
176        flag: &'static str,
177    },
178
179    #[error("Abstract types may only appear in constant expressions")]
180    Abstract,
181}
182
183#[derive(Clone, Debug, thiserror::Error)]
184#[cfg_attr(test, derive(PartialEq))]
185pub enum ImmediateError {
186    #[error("The scalar type {0:?} is not supported in immediates")]
187    InvalidScalar(crate::Scalar),
188}
189
190// Only makes sense if `flags.contains(HOST_SHAREABLE)`
191type LayoutCompatibility = Result<Alignment, (Handle<crate::Type>, Disalignment)>;
192type ImmediateCompatibility = Result<(), ImmediateError>;
193
194fn check_member_layout(
195    accum: &mut LayoutCompatibility,
196    member: &crate::StructMember,
197    member_index: u32,
198    member_layout: LayoutCompatibility,
199    parent_handle: Handle<crate::Type>,
200) {
201    *accum = match (*accum, member_layout) {
202        (Ok(cur_alignment), Ok(alignment)) => {
203            if alignment.is_aligned(member.offset) {
204                Ok(cur_alignment.max(alignment))
205            } else {
206                Err((
207                    parent_handle,
208                    Disalignment::MemberOffset {
209                        index: member_index,
210                        offset: member.offset,
211                        alignment,
212                    },
213                ))
214            }
215        }
216        (Err(e), _) | (_, Err(e)) => Err(e),
217    };
218}
219
220/// Determine whether a pointer in `space` can be passed as an argument.
221///
222/// If a pointer in `space` is permitted to be passed as an argument to a
223/// user-defined function, return `TypeFlags::ARGUMENT`. Otherwise, return
224/// `TypeFlags::empty()`.
225///
226/// Pointers passed as arguments to user-defined functions must be in the
227/// `Function` or `Private` address space.
228const fn ptr_space_argument_flag(space: crate::AddressSpace) -> TypeFlags {
229    use crate::AddressSpace as As;
230    match space {
231        As::Function | As::Private | As::RayPayload | As::IncomingRayPayload => TypeFlags::ARGUMENT,
232        As::Uniform
233        | As::Storage { .. }
234        | As::Handle
235        | As::Immediate
236        | As::WorkGroup
237        | As::TaskPayload => TypeFlags::empty(),
238    }
239}
240
241#[derive(Clone, Debug)]
242pub(super) struct TypeInfo {
243    pub flags: TypeFlags,
244    pub uniform_layout: LayoutCompatibility,
245    pub storage_layout: LayoutCompatibility,
246    pub immediates_compatibility: ImmediateCompatibility,
247}
248
249impl TypeInfo {
250    const fn dummy() -> Self {
251        TypeInfo {
252            flags: TypeFlags::empty(),
253            uniform_layout: Ok(Alignment::ONE),
254            storage_layout: Ok(Alignment::ONE),
255            immediates_compatibility: Ok(()),
256        }
257    }
258
259    const fn new(flags: TypeFlags, alignment: Alignment) -> Self {
260        TypeInfo {
261            flags,
262            uniform_layout: Ok(alignment),
263            storage_layout: Ok(alignment),
264            immediates_compatibility: Ok(()),
265        }
266    }
267}
268
269impl super::Validator {
270    const fn require_type_capability(&self, capability: Capabilities) -> Result<(), TypeError> {
271        if self.capabilities.contains(capability) {
272            Ok(())
273        } else {
274            Err(TypeError::MissingCapability(capability))
275        }
276    }
277
278    /// Check whether `scalar` is a permitted scalar width.
279    ///
280    /// If `scalar` is not a width allowed by the selected [`Capabilities`],
281    /// return an error explaining why.
282    ///
283    /// If `scalar` is allowed, return a [`ImmediateCompatibility`] result
284    /// that says whether `scalar` is allowed specifically in immediates.
285    ///
286    /// [`Capabilities`]: crate::valid::Capabilities
287    pub(super) const fn check_width(
288        &self,
289        scalar: crate::Scalar,
290    ) -> Result<ImmediateCompatibility, WidthError> {
291        let mut immediates_compatibility = Ok(());
292        let good = match scalar.kind {
293            crate::ScalarKind::Bool => scalar.width == crate::BOOL_WIDTH,
294            crate::ScalarKind::Float => match scalar.width {
295                8 => {
296                    if !self.capabilities.contains(Capabilities::FLOAT64) {
297                        return Err(WidthError::MissingCapability {
298                            name: "f64",
299                            flag: "FLOAT64",
300                        });
301                    }
302                    true
303                }
304                2 => {
305                    if !self.capabilities.contains(Capabilities::SHADER_FLOAT16) {
306                        return Err(WidthError::MissingCapability {
307                            name: "f16",
308                            flag: "FLOAT16",
309                        });
310                    }
311
312                    true
313                }
314                _ => scalar.width == 4,
315            },
316            crate::ScalarKind::Sint => {
317                if scalar.width == 2 {
318                    if !self.capabilities.contains(Capabilities::SHADER_INT16) {
319                        return Err(WidthError::MissingCapability {
320                            name: "i16",
321                            flag: "SHADER_INT16",
322                        });
323                    }
324
325                    immediates_compatibility = Err(ImmediateError::InvalidScalar(scalar));
326
327                    true
328                } else if scalar.width == 8 {
329                    if !self.capabilities.contains(Capabilities::SHADER_INT64) {
330                        return Err(WidthError::MissingCapability {
331                            name: "i64",
332                            flag: "SHADER_INT64",
333                        });
334                    }
335                    true
336                } else {
337                    scalar.width == 4
338                }
339            }
340            crate::ScalarKind::Uint => {
341                if scalar.width == 2 {
342                    if !self.capabilities.contains(Capabilities::SHADER_INT16) {
343                        return Err(WidthError::MissingCapability {
344                            name: "u16",
345                            flag: "SHADER_INT16",
346                        });
347                    }
348
349                    immediates_compatibility = Err(ImmediateError::InvalidScalar(scalar));
350
351                    true
352                } else if scalar.width == 8 {
353                    if !self.capabilities.contains(Capabilities::SHADER_INT64) {
354                        return Err(WidthError::MissingCapability {
355                            name: "u64",
356                            flag: "SHADER_INT64",
357                        });
358                    }
359                    true
360                } else {
361                    scalar.width == 4
362                }
363            }
364            crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => {
365                return Err(WidthError::Abstract);
366            }
367        };
368        if good {
369            Ok(immediates_compatibility)
370        } else {
371            Err(WidthError::Invalid(scalar.kind, scalar.width))
372        }
373    }
374
375    pub(super) fn reset_types(&mut self, size: usize) {
376        self.types.clear();
377        self.types.resize(size, TypeInfo::dummy());
378        self.layouter.clear();
379    }
380
381    pub(super) fn validate_type(
382        &self,
383        handle: Handle<crate::Type>,
384        gctx: crate::proc::GlobalCtx,
385    ) -> Result<TypeInfo, TypeError> {
386        use crate::TypeInner as Ti;
387        Ok(match gctx.types[handle].inner {
388            Ti::Scalar(scalar) => {
389                let immediates_compatibility = self.check_width(scalar)?;
390                let shareable = if scalar.kind.is_numeric() {
391                    TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
392                } else {
393                    TypeFlags::empty()
394                };
395                let mut type_info = TypeInfo::new(
396                    TypeFlags::DATA
397                        | TypeFlags::SIZED
398                        | TypeFlags::COPY
399                        | TypeFlags::ARGUMENT
400                        | TypeFlags::CONSTRUCTIBLE
401                        | TypeFlags::CREATION_RESOLVED
402                        | shareable,
403                    Alignment::from_width(scalar.width),
404                );
405                type_info.immediates_compatibility = immediates_compatibility;
406                type_info
407            }
408            Ti::Vector { size, scalar } => {
409                let immediates_compatibility = self.check_width(scalar)?;
410                let shareable = if scalar.kind.is_numeric() {
411                    TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
412                } else {
413                    TypeFlags::empty()
414                };
415                let mut type_info = TypeInfo::new(
416                    TypeFlags::DATA
417                        | TypeFlags::SIZED
418                        | TypeFlags::COPY
419                        | TypeFlags::ARGUMENT
420                        | TypeFlags::CONSTRUCTIBLE
421                        | TypeFlags::CREATION_RESOLVED
422                        | shareable,
423                    Alignment::from(size) * Alignment::from_width(scalar.width),
424                );
425                type_info.immediates_compatibility = immediates_compatibility;
426                type_info
427            }
428            Ti::Matrix {
429                columns: _,
430                rows,
431                scalar,
432            } => {
433                if scalar.kind != crate::ScalarKind::Float {
434                    return Err(TypeError::MatrixElementNotFloat);
435                }
436                let immediates_compatibility = self.check_width(scalar)?;
437                let mut type_info = TypeInfo::new(
438                    TypeFlags::DATA
439                        | TypeFlags::SIZED
440                        | TypeFlags::COPY
441                        | TypeFlags::HOST_SHAREABLE
442                        | TypeFlags::ARGUMENT
443                        | TypeFlags::CONSTRUCTIBLE
444                        | TypeFlags::CREATION_RESOLVED,
445                    Alignment::from(rows) * Alignment::from_width(scalar.width),
446                );
447                type_info.immediates_compatibility = immediates_compatibility;
448                type_info
449            }
450            Ti::CooperativeMatrix {
451                columns: _,
452                rows: _,
453                scalar,
454                role: _,
455            } => {
456                self.require_type_capability(Capabilities::COOPERATIVE_MATRIX)?;
457                // Allow f16 (width 2) and f32 (width 4) for cooperative matrices
458                if scalar.kind != crate::ScalarKind::Float
459                    || (scalar.width != 2 && scalar.width != 4)
460                {
461                    return Err(TypeError::MatrixElementNotFloat);
462                }
463                TypeInfo::new(
464                    TypeFlags::DATA
465                        | TypeFlags::SIZED
466                        | TypeFlags::COPY
467                        | TypeFlags::HOST_SHAREABLE
468                        | TypeFlags::ARGUMENT
469                        | TypeFlags::CONSTRUCTIBLE
470                        | TypeFlags::CREATION_RESOLVED,
471                    Alignment::from_width(scalar.width),
472                )
473            }
474            Ti::Atomic(scalar) => {
475                match scalar {
476                    crate::Scalar {
477                        kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint,
478                        width: 4,
479                    } => {}
480                    crate::Scalar {
481                        kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint,
482                        width: 8,
483                    } => {
484                        if !self.capabilities.intersects(
485                            Capabilities::SHADER_INT64_ATOMIC_ALL_OPS
486                                | Capabilities::SHADER_INT64_ATOMIC_MIN_MAX,
487                        ) {
488                            return Err(TypeError::MissingCapability(
489                                Capabilities::SHADER_INT64_ATOMIC_ALL_OPS,
490                            ));
491                        }
492                    }
493                    crate::Scalar::F32 => {
494                        if !self
495                            .capabilities
496                            .contains(Capabilities::SHADER_FLOAT32_ATOMIC)
497                        {
498                            return Err(TypeError::MissingCapability(
499                                Capabilities::SHADER_FLOAT32_ATOMIC,
500                            ));
501                        }
502                    }
503                    _ => return Err(TypeError::InvalidAtomicWidth(scalar.kind, scalar.width)),
504                };
505                TypeInfo::new(
506                    TypeFlags::DATA
507                        | TypeFlags::SIZED
508                        | TypeFlags::HOST_SHAREABLE
509                        | TypeFlags::CREATION_RESOLVED,
510                    Alignment::from_width(scalar.width),
511                )
512            }
513            Ti::Pointer { base, space } => {
514                use crate::AddressSpace as As;
515
516                let base_info = &self.types[base.index()];
517                if !base_info.flags.contains(TypeFlags::DATA) {
518                    return Err(TypeError::InvalidPointerBase(base));
519                }
520
521                // Runtime-sized values can only live in the `Storage` address
522                // space, so it's useless to have a pointer to such a type in
523                // any other space.
524                //
525                // Detecting this problem here prevents the definition of
526                // functions like:
527                //
528                //     fn f(p: ptr<workgroup, UnsizedType>) -> ... { ... }
529                //
530                // which would otherwise be permitted, but uncallable. (They
531                // may also present difficulties in code generation).
532                if !base_info.flags.contains(TypeFlags::SIZED) {
533                    match space {
534                        As::Storage { .. } => {}
535                        _ => {
536                            return Err(TypeError::InvalidPointerToUnsized { base, space });
537                        }
538                    }
539                }
540
541                // `Validator::validate_function` actually checks the address
542                // space of pointer arguments explicitly before checking the
543                // `ARGUMENT` flag, to give better error messages. But it seems
544                // best to set `ARGUMENT` accurately anyway.
545                let argument_flag = ptr_space_argument_flag(space);
546
547                // Pointers cannot be stored in variables, structure members, or
548                // array elements, so we do not mark them as `DATA`.
549                TypeInfo::new(
550                    argument_flag
551                        | TypeFlags::SIZED
552                        | TypeFlags::COPY
553                        | TypeFlags::CREATION_RESOLVED,
554                    Alignment::ONE,
555                )
556            }
557            Ti::ValuePointer {
558                size: _,
559                scalar,
560                space,
561            } => {
562                // ValuePointer should be treated the same way as the equivalent
563                // Pointer / Scalar / Vector combination, so each step in those
564                // variants' match arms should have a counterpart here.
565                //
566                // However, some cases are trivial: All our implicit base types
567                // are DATA and SIZED, so we can never return
568                // `InvalidPointerBase` or `InvalidPointerToUnsized`.
569                let _ = self.check_width(scalar)?;
570
571                // `Validator::validate_function` actually checks the address
572                // space of pointer arguments explicitly before checking the
573                // `ARGUMENT` flag, to give better error messages. But it seems
574                // best to set `ARGUMENT` accurately anyway.
575                let argument_flag = ptr_space_argument_flag(space);
576
577                // Pointers cannot be stored in variables, structure members, or
578                // array elements, so we do not mark them as `DATA`.
579                TypeInfo::new(
580                    argument_flag
581                        | TypeFlags::SIZED
582                        | TypeFlags::COPY
583                        | TypeFlags::CREATION_RESOLVED,
584                    Alignment::ONE,
585                )
586            }
587            Ti::Array { base, size, stride } => {
588                let base_info = &self.types[base.index()];
589                if !base_info
590                    .flags
591                    .contains(TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::CREATION_RESOLVED)
592                {
593                    return Err(TypeError::InvalidArrayBaseType(base));
594                }
595
596                if self.overrides_resolved {
597                    // This check only makes sense for override-sized arrays.
598                    // `ArraySize::Constant` holds a `NonZeroU32`.
599                    if let crate::ArraySize::Pending(_) = size {
600                        size.resolve(gctx)
601                            .map_err(|_| TypeError::InvalidArraySize(handle))?;
602                    }
603                }
604
605                let base_layout = self.layouter[base];
606                let general_alignment = base_layout.alignment;
607                let uniform_layout = match base_info.uniform_layout {
608                    Ok(base_alignment) => {
609                        let alignment = base_alignment
610                            .max(general_alignment)
611                            .max(Alignment::MIN_UNIFORM);
612                        if alignment.is_aligned(stride) {
613                            Ok(alignment)
614                        } else {
615                            Err((handle, Disalignment::ArrayStride { stride, alignment }))
616                        }
617                    }
618                    Err(e) => Err(e),
619                };
620                let storage_layout = match base_info.storage_layout {
621                    Ok(base_alignment) => {
622                        let alignment = base_alignment.max(general_alignment);
623                        if alignment.is_aligned(stride) {
624                            Ok(alignment)
625                        } else {
626                            Err((handle, Disalignment::ArrayStride { stride, alignment }))
627                        }
628                    }
629                    Err(e) => Err(e),
630                };
631
632                let type_info_mask = match size {
633                    crate::ArraySize::Constant(_) => {
634                        TypeFlags::DATA
635                            | TypeFlags::SIZED
636                            | TypeFlags::COPY
637                            | TypeFlags::HOST_SHAREABLE
638                            | TypeFlags::ARGUMENT
639                            | TypeFlags::CONSTRUCTIBLE
640                            | TypeFlags::CREATION_RESOLVED
641                    }
642                    crate::ArraySize::Pending(_) => {
643                        TypeFlags::DATA
644                            | TypeFlags::SIZED
645                            | TypeFlags::COPY
646                            | TypeFlags::HOST_SHAREABLE
647                            | TypeFlags::ARGUMENT
648                    }
649                    crate::ArraySize::Dynamic => {
650                        // Non-SIZED types may only appear as the last element of a structure.
651                        // This is enforced by checks for SIZED-ness for all compound types,
652                        // and a special case for structs.
653                        TypeFlags::DATA
654                            | TypeFlags::COPY
655                            | TypeFlags::HOST_SHAREABLE
656                            | TypeFlags::CREATION_RESOLVED
657                    }
658                };
659
660                TypeInfo {
661                    flags: base_info.flags & type_info_mask,
662                    uniform_layout,
663                    storage_layout,
664                    immediates_compatibility: base_info.immediates_compatibility.clone(),
665                }
666            }
667            Ti::Struct { ref members, span } => {
668                if members.is_empty() {
669                    return Err(TypeError::EmptyStruct);
670                }
671
672                let mut blend_src_types = [None, None];
673                let mut non_blend_src_location = None;
674
675                let mut ti = TypeInfo::new(
676                    TypeFlags::DATA
677                        | TypeFlags::SIZED
678                        | TypeFlags::COPY
679                        | TypeFlags::HOST_SHAREABLE
680                        | TypeFlags::ARGUMENT
681                        | TypeFlags::CONSTRUCTIBLE
682                        | TypeFlags::CREATION_RESOLVED,
683                    Alignment::ONE,
684                );
685                ti.uniform_layout = Ok(Alignment::MIN_UNIFORM);
686
687                let mut min_offset = 0;
688                let mut prev_struct_data: Option<(u32, u32)> = None;
689
690                for (i, member) in members.iter().enumerate() {
691                    let base_info = &self.types[member.ty.index()];
692                    if !base_info
693                        .flags
694                        .contains(TypeFlags::DATA | TypeFlags::CREATION_RESOLVED)
695                    {
696                        return Err(TypeError::InvalidData(member.ty));
697                    }
698                    if !base_info.flags.contains(TypeFlags::HOST_SHAREABLE) {
699                        if ti.uniform_layout.is_ok() {
700                            ti.uniform_layout = Err((member.ty, Disalignment::NonHostShareable));
701                        }
702                        if ti.storage_layout.is_ok() {
703                            ti.storage_layout = Err((member.ty, Disalignment::NonHostShareable));
704                        }
705                    }
706                    ti.flags &= base_info.flags;
707
708                    match member.binding {
709                        Some(ir::Binding::Location {
710                            location,
711                            blend_src: Some(blend_src),
712                            ..
713                        }) => {
714                            // `blend_src` is only valid if dual source blending was explicitly enabled,
715                            // see https://www.w3.org/TR/WGSL/#extension-dual_source_blending
716                            if !self
717                                .capabilities
718                                .contains(Capabilities::DUAL_SOURCE_BLENDING)
719                            {
720                                return Err(TypeError::MissingCapability(
721                                    Capabilities::DUAL_SOURCE_BLENDING,
722                                ));
723                            }
724                            if !(location == 0 && (blend_src == 0 || blend_src == 1)) {
725                                return Err(TypeError::InvalidBlendSrc(
726                                    super::VaryingError::InvalidBlendSrcIndex {
727                                        location,
728                                        blend_src,
729                                    },
730                                ));
731                            }
732                            if blend_src_types[blend_src as usize]
733                                .replace(member.ty)
734                                .is_some()
735                            {
736                                // @blend_src(i) appeared multiple times
737                                return Err(TypeError::InvalidBlendSrc(
738                                    super::VaryingError::BindingCollisionBlendSrc { blend_src },
739                                ));
740                            }
741                        }
742                        Some(ir::Binding::Location {
743                            location,
744                            blend_src: None,
745                            ..
746                        }) => non_blend_src_location = Some(location),
747                        _ => {}
748                    }
749
750                    if member.offset < min_offset {
751                        // HACK: this could be nicer. We want to allow some structures
752                        // to not bother with offsets/alignments if they are never
753                        // used for host sharing.
754                        if member.offset == 0 {
755                            ti.flags.set(TypeFlags::HOST_SHAREABLE, false);
756                        } else {
757                            return Err(TypeError::MemberOverlap {
758                                index: i as u32,
759                                offset: member.offset,
760                            });
761                        }
762                    }
763
764                    let base_size = gctx.types[member.ty].inner.size(gctx);
765                    min_offset = member.offset + base_size;
766                    if min_offset > span {
767                        return Err(TypeError::MemberOutOfBounds {
768                            index: i as u32,
769                            offset: member.offset,
770                            size: base_size,
771                            span,
772                        });
773                    }
774
775                    check_member_layout(
776                        &mut ti.uniform_layout,
777                        member,
778                        i as u32,
779                        base_info.uniform_layout,
780                        handle,
781                    );
782                    check_member_layout(
783                        &mut ti.storage_layout,
784                        member,
785                        i as u32,
786                        base_info.storage_layout,
787                        handle,
788                    );
789                    if base_info.immediates_compatibility.is_err() {
790                        ti.immediates_compatibility = base_info.immediates_compatibility.clone();
791                    }
792
793                    // Validate rule: If a structure member itself has a structure type S,
794                    // then the number of bytes between the start of that member and
795                    // the start of any following member must be at least roundUp(16, SizeOf(S)).
796                    if let Some((span, offset)) = prev_struct_data {
797                        let diff = member.offset - offset;
798                        let min = Alignment::MIN_UNIFORM.round_up(span);
799                        if diff < min {
800                            ti.uniform_layout = Err((
801                                handle,
802                                Disalignment::MemberOffsetAfterStruct {
803                                    index: i as u32,
804                                    offset: member.offset,
805                                    expected: offset + min,
806                                },
807                            ));
808                        }
809                    };
810
811                    prev_struct_data = match gctx.types[member.ty].inner {
812                        crate::TypeInner::Struct { span, .. } => Some((span, member.offset)),
813                        _ => None,
814                    };
815
816                    // The last field may be an unsized array.
817                    if !base_info.flags.contains(TypeFlags::SIZED) {
818                        let is_array = match gctx.types[member.ty].inner {
819                            crate::TypeInner::Array { .. } => true,
820                            _ => false,
821                        };
822                        if !is_array || i + 1 != members.len() {
823                            let name = member.name.clone().unwrap_or_default();
824                            return Err(TypeError::InvalidDynamicArray(name, member.ty));
825                        }
826                        if ti.uniform_layout.is_ok() {
827                            ti.uniform_layout =
828                                Err((handle, Disalignment::UnsizedMember { index: i as u32 }));
829                        }
830                    }
831                }
832
833                match blend_src_types {
834                    [None, None] => {}
835                    [Some(ty0), Some(ty1)] => {
836                        if let Some(location) = non_blend_src_location {
837                            // If `@blend_src` members are present, then `@location`
838                            // may only be used for those members.
839                            return Err(TypeError::InvalidBlendSrc(
840                                super::VaryingError::InvalidBlendSrcWithOtherBindings { location },
841                            ));
842                        }
843                        let ty0_inner = &gctx.types[ty0].inner;
844                        let ty1_inner = &gctx.types[ty1].inner;
845                        // The two blend sources must have the same type...
846                        if !ty0_inner.non_struct_equivalent(ty1_inner, gctx.types) {
847                            return Err(TypeError::InvalidBlendSrc(
848                                super::VaryingError::BlendSrcOutputTypeMismatch {
849                                    blend_src_0_type: ty0,
850                                    blend_src_1_type: ty1,
851                                },
852                            ));
853                        }
854                        // ... and that type must be I/O-shareable.
855                        if !self.types[ty0.index()]
856                            .flags
857                            .contains(TypeFlags::IO_SHAREABLE)
858                        {
859                            return Err(TypeError::InvalidBlendSrc(
860                                super::VaryingError::NotIOShareableType(ty0),
861                            ));
862                        }
863
864                        // `@blend_src` is the only case where we classify a struct as
865                        // I/O-shareable. (In the case of a struct with `@location` bindings, we
866                        // process the members individually in interface validation, and do not
867                        // classify the struct as I/O-shareable.)
868                        ti.flags |= TypeFlags::IO_SHAREABLE;
869                    }
870                    [None, Some(_)] | [Some(_), None] => {
871                        // Only one of the blend sources was specified.
872                        return Err(TypeError::InvalidBlendSrc(
873                            super::VaryingError::IncompleteBlendSrcUsage {
874                                present_blend_src: blend_src_types
875                                    .iter()
876                                    .position(|src| src.is_some())
877                                    .unwrap()
878                                    as u32,
879                            },
880                        ));
881                    }
882                }
883
884                let alignment = self.layouter[handle].alignment;
885                if !alignment.is_aligned(span) {
886                    ti.uniform_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
887                    ti.storage_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
888                }
889
890                ti
891            }
892            Ti::Image {
893                dim,
894                arrayed,
895                class,
896            } => {
897                if arrayed && matches!(dim, crate::ImageDimension::D3) {
898                    return Err(TypeError::UnsupportedImageType {
899                        dim,
900                        arrayed,
901                        class,
902                    });
903                }
904                if arrayed && matches!(dim, crate::ImageDimension::Cube) {
905                    self.require_type_capability(Capabilities::CUBE_ARRAY_TEXTURES)?;
906                }
907                if matches!(class, crate::ImageClass::External) {
908                    if dim != crate::ImageDimension::D2 || arrayed {
909                        return Err(TypeError::UnsupportedImageType {
910                            dim,
911                            arrayed,
912                            class,
913                        });
914                    }
915                    self.require_type_capability(Capabilities::TEXTURE_EXTERNAL)?;
916                }
917                TypeInfo::new(
918                    TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
919                    Alignment::ONE,
920                )
921            }
922            Ti::Sampler { .. } => TypeInfo::new(
923                TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
924                Alignment::ONE,
925            ),
926            Ti::AccelerationStructure { vertex_return } => {
927                self.require_type_capability(Capabilities::RAY_TRACING_PIPELINE)
928                    .or_else(|_| self.require_type_capability(Capabilities::RAY_QUERY))?;
929                if vertex_return {
930                    self.require_type_capability(Capabilities::RAY_HIT_VERTEX_POSITION)?;
931                }
932                TypeInfo::new(
933                    TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
934                    Alignment::ONE,
935                )
936            }
937            Ti::RayQuery { vertex_return } => {
938                self.require_type_capability(Capabilities::RAY_QUERY)?;
939                if vertex_return {
940                    self.require_type_capability(Capabilities::RAY_HIT_VERTEX_POSITION)?;
941                }
942                TypeInfo::new(
943                    TypeFlags::DATA
944                        | TypeFlags::CONSTRUCTIBLE
945                        | TypeFlags::SIZED
946                        | TypeFlags::CREATION_RESOLVED,
947                    Alignment::ONE,
948                )
949            }
950            Ti::BindingArray { base, size } => {
951                let type_info_mask = match size {
952                    crate::ArraySize::Constant(_) => {
953                        TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED
954                    }
955                    crate::ArraySize::Pending(_) => TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE,
956                    crate::ArraySize::Dynamic => {
957                        // Final type is non-sized
958                        TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED
959                    }
960                };
961                let base_info = &self.types[base.index()];
962
963                if base_info.flags.contains(TypeFlags::DATA) {
964                    // Currently Naga only supports binding arrays of structs for non-handle types.
965                    // `validate_global_var` relies on ray queries (which are `DATA`) being rejected here
966                    match gctx.types[base].inner {
967                        crate::TypeInner::Struct { .. } => {}
968                        _ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)),
969                    };
970                }
971                if matches!(
972                    gctx.types[base].inner,
973                    crate::TypeInner::Image {
974                        class: crate::ImageClass::External,
975                        ..
976                    }
977                ) {
978                    // Binding arrays of external textures are not yet supported.
979                    // See <https://github.com/gfx-rs/wgpu/issues/8027>. Note that
980                    // `validate_global_var` relies on this error being raised here.
981                    return Err(TypeError::BindingArrayBaseExternalTextures);
982                }
983
984                if !base_info.flags.contains(TypeFlags::CREATION_RESOLVED) {
985                    return Err(TypeError::InvalidData(base));
986                }
987
988                TypeInfo::new(base_info.flags & type_info_mask, Alignment::ONE)
989            }
990        })
991    }
992}