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                    immediates_compatibility = Err(ImmediateError::InvalidScalar(scalar));
313
314                    true
315                }
316                _ => scalar.width == 4,
317            },
318            crate::ScalarKind::Sint => {
319                if scalar.width == 8 {
320                    if !self.capabilities.contains(Capabilities::SHADER_INT64) {
321                        return Err(WidthError::MissingCapability {
322                            name: "i64",
323                            flag: "SHADER_INT64",
324                        });
325                    }
326                    true
327                } else {
328                    scalar.width == 4
329                }
330            }
331            crate::ScalarKind::Uint => {
332                if scalar.width == 8 {
333                    if !self.capabilities.contains(Capabilities::SHADER_INT64) {
334                        return Err(WidthError::MissingCapability {
335                            name: "u64",
336                            flag: "SHADER_INT64",
337                        });
338                    }
339                    true
340                } else {
341                    scalar.width == 4
342                }
343            }
344            crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => {
345                return Err(WidthError::Abstract);
346            }
347        };
348        if good {
349            Ok(immediates_compatibility)
350        } else {
351            Err(WidthError::Invalid(scalar.kind, scalar.width))
352        }
353    }
354
355    pub(super) fn reset_types(&mut self, size: usize) {
356        self.types.clear();
357        self.types.resize(size, TypeInfo::dummy());
358        self.layouter.clear();
359    }
360
361    pub(super) fn validate_type(
362        &self,
363        handle: Handle<crate::Type>,
364        gctx: crate::proc::GlobalCtx,
365    ) -> Result<TypeInfo, TypeError> {
366        use crate::TypeInner as Ti;
367        Ok(match gctx.types[handle].inner {
368            Ti::Scalar(scalar) => {
369                let immediates_compatibility = self.check_width(scalar)?;
370                let shareable = if scalar.kind.is_numeric() {
371                    TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
372                } else {
373                    TypeFlags::empty()
374                };
375                let mut type_info = TypeInfo::new(
376                    TypeFlags::DATA
377                        | TypeFlags::SIZED
378                        | TypeFlags::COPY
379                        | TypeFlags::ARGUMENT
380                        | TypeFlags::CONSTRUCTIBLE
381                        | TypeFlags::CREATION_RESOLVED
382                        | shareable,
383                    Alignment::from_width(scalar.width),
384                );
385                type_info.immediates_compatibility = immediates_compatibility;
386                type_info
387            }
388            Ti::Vector { size, 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(size) * Alignment::from_width(scalar.width),
404                );
405                type_info.immediates_compatibility = immediates_compatibility;
406                type_info
407            }
408            Ti::Matrix {
409                columns: _,
410                rows,
411                scalar,
412            } => {
413                if scalar.kind != crate::ScalarKind::Float {
414                    return Err(TypeError::MatrixElementNotFloat);
415                }
416                let immediates_compatibility = self.check_width(scalar)?;
417                let mut type_info = TypeInfo::new(
418                    TypeFlags::DATA
419                        | TypeFlags::SIZED
420                        | TypeFlags::COPY
421                        | TypeFlags::HOST_SHAREABLE
422                        | TypeFlags::ARGUMENT
423                        | TypeFlags::CONSTRUCTIBLE
424                        | TypeFlags::CREATION_RESOLVED,
425                    Alignment::from(rows) * Alignment::from_width(scalar.width),
426                );
427                type_info.immediates_compatibility = immediates_compatibility;
428                type_info
429            }
430            Ti::CooperativeMatrix {
431                columns: _,
432                rows: _,
433                scalar,
434                role: _,
435            } => {
436                self.require_type_capability(Capabilities::COOPERATIVE_MATRIX)?;
437                // Allow f16 (width 2) and f32 (width 4) for cooperative matrices
438                if scalar.kind != crate::ScalarKind::Float
439                    || (scalar.width != 2 && scalar.width != 4)
440                {
441                    return Err(TypeError::MatrixElementNotFloat);
442                }
443                TypeInfo::new(
444                    TypeFlags::DATA
445                        | TypeFlags::SIZED
446                        | TypeFlags::COPY
447                        | TypeFlags::HOST_SHAREABLE
448                        | TypeFlags::ARGUMENT
449                        | TypeFlags::CONSTRUCTIBLE
450                        | TypeFlags::CREATION_RESOLVED,
451                    Alignment::from_width(scalar.width),
452                )
453            }
454            Ti::Atomic(scalar) => {
455                match scalar {
456                    crate::Scalar {
457                        kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint,
458                        width: _,
459                    } => {
460                        if scalar.width == 8
461                            && !self.capabilities.intersects(
462                                Capabilities::SHADER_INT64_ATOMIC_ALL_OPS
463                                    | Capabilities::SHADER_INT64_ATOMIC_MIN_MAX,
464                            )
465                        {
466                            return Err(TypeError::MissingCapability(
467                                Capabilities::SHADER_INT64_ATOMIC_ALL_OPS,
468                            ));
469                        }
470                    }
471                    crate::Scalar::F32 => {
472                        if !self
473                            .capabilities
474                            .contains(Capabilities::SHADER_FLOAT32_ATOMIC)
475                        {
476                            return Err(TypeError::MissingCapability(
477                                Capabilities::SHADER_FLOAT32_ATOMIC,
478                            ));
479                        }
480                    }
481                    _ => return Err(TypeError::InvalidAtomicWidth(scalar.kind, scalar.width)),
482                };
483                TypeInfo::new(
484                    TypeFlags::DATA
485                        | TypeFlags::SIZED
486                        | TypeFlags::HOST_SHAREABLE
487                        | TypeFlags::CREATION_RESOLVED,
488                    Alignment::from_width(scalar.width),
489                )
490            }
491            Ti::Pointer { base, space } => {
492                use crate::AddressSpace as As;
493
494                let base_info = &self.types[base.index()];
495                if !base_info.flags.contains(TypeFlags::DATA) {
496                    return Err(TypeError::InvalidPointerBase(base));
497                }
498
499                // Runtime-sized values can only live in the `Storage` address
500                // space, so it's useless to have a pointer to such a type in
501                // any other space.
502                //
503                // Detecting this problem here prevents the definition of
504                // functions like:
505                //
506                //     fn f(p: ptr<workgroup, UnsizedType>) -> ... { ... }
507                //
508                // which would otherwise be permitted, but uncallable. (They
509                // may also present difficulties in code generation).
510                if !base_info.flags.contains(TypeFlags::SIZED) {
511                    match space {
512                        As::Storage { .. } => {}
513                        _ => {
514                            return Err(TypeError::InvalidPointerToUnsized { base, space });
515                        }
516                    }
517                }
518
519                // `Validator::validate_function` actually checks the address
520                // space of pointer arguments explicitly before checking the
521                // `ARGUMENT` flag, to give better error messages. But it seems
522                // best to set `ARGUMENT` accurately anyway.
523                let argument_flag = ptr_space_argument_flag(space);
524
525                // Pointers cannot be stored in variables, structure members, or
526                // array elements, so we do not mark them as `DATA`.
527                TypeInfo::new(
528                    argument_flag
529                        | TypeFlags::SIZED
530                        | TypeFlags::COPY
531                        | TypeFlags::CREATION_RESOLVED,
532                    Alignment::ONE,
533                )
534            }
535            Ti::ValuePointer {
536                size: _,
537                scalar,
538                space,
539            } => {
540                // ValuePointer should be treated the same way as the equivalent
541                // Pointer / Scalar / Vector combination, so each step in those
542                // variants' match arms should have a counterpart here.
543                //
544                // However, some cases are trivial: All our implicit base types
545                // are DATA and SIZED, so we can never return
546                // `InvalidPointerBase` or `InvalidPointerToUnsized`.
547                let _ = self.check_width(scalar)?;
548
549                // `Validator::validate_function` actually checks the address
550                // space of pointer arguments explicitly before checking the
551                // `ARGUMENT` flag, to give better error messages. But it seems
552                // best to set `ARGUMENT` accurately anyway.
553                let argument_flag = ptr_space_argument_flag(space);
554
555                // Pointers cannot be stored in variables, structure members, or
556                // array elements, so we do not mark them as `DATA`.
557                TypeInfo::new(
558                    argument_flag
559                        | TypeFlags::SIZED
560                        | TypeFlags::COPY
561                        | TypeFlags::CREATION_RESOLVED,
562                    Alignment::ONE,
563                )
564            }
565            Ti::Array { base, size, stride } => {
566                let base_info = &self.types[base.index()];
567                if !base_info
568                    .flags
569                    .contains(TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::CREATION_RESOLVED)
570                {
571                    return Err(TypeError::InvalidArrayBaseType(base));
572                }
573
574                if self.overrides_resolved {
575                    // This check only makes sense for override-sized arrays.
576                    // `ArraySize::Constant` holds a `NonZeroU32`.
577                    if let crate::ArraySize::Pending(_) = size {
578                        size.resolve(gctx)
579                            .map_err(|_| TypeError::InvalidArraySize(handle))?;
580                    }
581                }
582
583                let base_layout = self.layouter[base];
584                let general_alignment = base_layout.alignment;
585                let uniform_layout = match base_info.uniform_layout {
586                    Ok(base_alignment) => {
587                        let alignment = base_alignment
588                            .max(general_alignment)
589                            .max(Alignment::MIN_UNIFORM);
590                        if alignment.is_aligned(stride) {
591                            Ok(alignment)
592                        } else {
593                            Err((handle, Disalignment::ArrayStride { stride, alignment }))
594                        }
595                    }
596                    Err(e) => Err(e),
597                };
598                let storage_layout = match base_info.storage_layout {
599                    Ok(base_alignment) => {
600                        let alignment = base_alignment.max(general_alignment);
601                        if alignment.is_aligned(stride) {
602                            Ok(alignment)
603                        } else {
604                            Err((handle, Disalignment::ArrayStride { stride, alignment }))
605                        }
606                    }
607                    Err(e) => Err(e),
608                };
609
610                let type_info_mask = match size {
611                    crate::ArraySize::Constant(_) => {
612                        TypeFlags::DATA
613                            | TypeFlags::SIZED
614                            | TypeFlags::COPY
615                            | TypeFlags::HOST_SHAREABLE
616                            | TypeFlags::ARGUMENT
617                            | TypeFlags::CONSTRUCTIBLE
618                            | TypeFlags::CREATION_RESOLVED
619                    }
620                    crate::ArraySize::Pending(_) => {
621                        TypeFlags::DATA
622                            | TypeFlags::SIZED
623                            | TypeFlags::COPY
624                            | TypeFlags::HOST_SHAREABLE
625                            | TypeFlags::ARGUMENT
626                    }
627                    crate::ArraySize::Dynamic => {
628                        // Non-SIZED types may only appear as the last element of a structure.
629                        // This is enforced by checks for SIZED-ness for all compound types,
630                        // and a special case for structs.
631                        TypeFlags::DATA
632                            | TypeFlags::COPY
633                            | TypeFlags::HOST_SHAREABLE
634                            | TypeFlags::CREATION_RESOLVED
635                    }
636                };
637
638                TypeInfo {
639                    flags: base_info.flags & type_info_mask,
640                    uniform_layout,
641                    storage_layout,
642                    immediates_compatibility: base_info.immediates_compatibility.clone(),
643                }
644            }
645            Ti::Struct { ref members, span } => {
646                if members.is_empty() {
647                    return Err(TypeError::EmptyStruct);
648                }
649
650                let mut blend_src_types = [None, None];
651                let mut non_blend_src_location = None;
652
653                let mut ti = TypeInfo::new(
654                    TypeFlags::DATA
655                        | TypeFlags::SIZED
656                        | TypeFlags::COPY
657                        | TypeFlags::HOST_SHAREABLE
658                        | TypeFlags::ARGUMENT
659                        | TypeFlags::CONSTRUCTIBLE
660                        | TypeFlags::CREATION_RESOLVED,
661                    Alignment::ONE,
662                );
663                ti.uniform_layout = Ok(Alignment::MIN_UNIFORM);
664
665                let mut min_offset = 0;
666                let mut prev_struct_data: Option<(u32, u32)> = None;
667
668                for (i, member) in members.iter().enumerate() {
669                    let base_info = &self.types[member.ty.index()];
670                    if !base_info
671                        .flags
672                        .contains(TypeFlags::DATA | TypeFlags::CREATION_RESOLVED)
673                    {
674                        return Err(TypeError::InvalidData(member.ty));
675                    }
676                    if !base_info.flags.contains(TypeFlags::HOST_SHAREABLE) {
677                        if ti.uniform_layout.is_ok() {
678                            ti.uniform_layout = Err((member.ty, Disalignment::NonHostShareable));
679                        }
680                        if ti.storage_layout.is_ok() {
681                            ti.storage_layout = Err((member.ty, Disalignment::NonHostShareable));
682                        }
683                    }
684                    ti.flags &= base_info.flags;
685
686                    match member.binding {
687                        Some(ir::Binding::Location {
688                            location,
689                            blend_src: Some(blend_src),
690                            ..
691                        }) => {
692                            // `blend_src` is only valid if dual source blending was explicitly enabled,
693                            // see https://www.w3.org/TR/WGSL/#extension-dual_source_blending
694                            if !self
695                                .capabilities
696                                .contains(Capabilities::DUAL_SOURCE_BLENDING)
697                            {
698                                return Err(TypeError::MissingCapability(
699                                    Capabilities::DUAL_SOURCE_BLENDING,
700                                ));
701                            }
702                            if !(location == 0 && (blend_src == 0 || blend_src == 1)) {
703                                return Err(TypeError::InvalidBlendSrc(
704                                    super::VaryingError::InvalidBlendSrcIndex {
705                                        location,
706                                        blend_src,
707                                    },
708                                ));
709                            }
710                            if blend_src_types[blend_src as usize]
711                                .replace(member.ty)
712                                .is_some()
713                            {
714                                // @blend_src(i) appeared multiple times
715                                return Err(TypeError::InvalidBlendSrc(
716                                    super::VaryingError::BindingCollisionBlendSrc { blend_src },
717                                ));
718                            }
719                        }
720                        Some(ir::Binding::Location {
721                            location,
722                            blend_src: None,
723                            ..
724                        }) => non_blend_src_location = Some(location),
725                        _ => {}
726                    }
727
728                    if member.offset < min_offset {
729                        // HACK: this could be nicer. We want to allow some structures
730                        // to not bother with offsets/alignments if they are never
731                        // used for host sharing.
732                        if member.offset == 0 {
733                            ti.flags.set(TypeFlags::HOST_SHAREABLE, false);
734                        } else {
735                            return Err(TypeError::MemberOverlap {
736                                index: i as u32,
737                                offset: member.offset,
738                            });
739                        }
740                    }
741
742                    let base_size = gctx.types[member.ty].inner.size(gctx);
743                    min_offset = member.offset + base_size;
744                    if min_offset > span {
745                        return Err(TypeError::MemberOutOfBounds {
746                            index: i as u32,
747                            offset: member.offset,
748                            size: base_size,
749                            span,
750                        });
751                    }
752
753                    check_member_layout(
754                        &mut ti.uniform_layout,
755                        member,
756                        i as u32,
757                        base_info.uniform_layout,
758                        handle,
759                    );
760                    check_member_layout(
761                        &mut ti.storage_layout,
762                        member,
763                        i as u32,
764                        base_info.storage_layout,
765                        handle,
766                    );
767                    if base_info.immediates_compatibility.is_err() {
768                        ti.immediates_compatibility = base_info.immediates_compatibility.clone();
769                    }
770
771                    // Validate rule: If a structure member itself has a structure type S,
772                    // then the number of bytes between the start of that member and
773                    // the start of any following member must be at least roundUp(16, SizeOf(S)).
774                    if let Some((span, offset)) = prev_struct_data {
775                        let diff = member.offset - offset;
776                        let min = Alignment::MIN_UNIFORM.round_up(span);
777                        if diff < min {
778                            ti.uniform_layout = Err((
779                                handle,
780                                Disalignment::MemberOffsetAfterStruct {
781                                    index: i as u32,
782                                    offset: member.offset,
783                                    expected: offset + min,
784                                },
785                            ));
786                        }
787                    };
788
789                    prev_struct_data = match gctx.types[member.ty].inner {
790                        crate::TypeInner::Struct { span, .. } => Some((span, member.offset)),
791                        _ => None,
792                    };
793
794                    // The last field may be an unsized array.
795                    if !base_info.flags.contains(TypeFlags::SIZED) {
796                        let is_array = match gctx.types[member.ty].inner {
797                            crate::TypeInner::Array { .. } => true,
798                            _ => false,
799                        };
800                        if !is_array || i + 1 != members.len() {
801                            let name = member.name.clone().unwrap_or_default();
802                            return Err(TypeError::InvalidDynamicArray(name, member.ty));
803                        }
804                        if ti.uniform_layout.is_ok() {
805                            ti.uniform_layout =
806                                Err((handle, Disalignment::UnsizedMember { index: i as u32 }));
807                        }
808                    }
809                }
810
811                match blend_src_types {
812                    [None, None] => {}
813                    [Some(ty0), Some(ty1)] => {
814                        if let Some(location) = non_blend_src_location {
815                            // If `@blend_src` members are present, then `@location`
816                            // may only be used for those members.
817                            return Err(TypeError::InvalidBlendSrc(
818                                super::VaryingError::InvalidBlendSrcWithOtherBindings { location },
819                            ));
820                        }
821                        let ty0_inner = &gctx.types[ty0].inner;
822                        let ty1_inner = &gctx.types[ty1].inner;
823                        // The two blend sources must have the same type...
824                        if !ty0_inner.non_struct_equivalent(ty1_inner, gctx.types) {
825                            return Err(TypeError::InvalidBlendSrc(
826                                super::VaryingError::BlendSrcOutputTypeMismatch {
827                                    blend_src_0_type: ty0,
828                                    blend_src_1_type: ty1,
829                                },
830                            ));
831                        }
832                        // ... and that type must be I/O-shareable.
833                        if !self.types[ty0.index()]
834                            .flags
835                            .contains(TypeFlags::IO_SHAREABLE)
836                        {
837                            return Err(TypeError::InvalidBlendSrc(
838                                super::VaryingError::NotIOShareableType(ty0),
839                            ));
840                        }
841
842                        // `@blend_src` is the only case where we classify a struct as
843                        // I/O-shareable. (In the case of a struct with `@location` bindings, we
844                        // process the members individually in interface validation, and do not
845                        // classify the struct as I/O-shareable.)
846                        ti.flags |= TypeFlags::IO_SHAREABLE;
847                    }
848                    [None, Some(_)] | [Some(_), None] => {
849                        // Only one of the blend sources was specified.
850                        return Err(TypeError::InvalidBlendSrc(
851                            super::VaryingError::IncompleteBlendSrcUsage {
852                                present_blend_src: blend_src_types
853                                    .iter()
854                                    .position(|src| src.is_some())
855                                    .unwrap()
856                                    as u32,
857                            },
858                        ));
859                    }
860                }
861
862                let alignment = self.layouter[handle].alignment;
863                if !alignment.is_aligned(span) {
864                    ti.uniform_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
865                    ti.storage_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
866                }
867
868                ti
869            }
870            Ti::Image {
871                dim,
872                arrayed,
873                class,
874            } => {
875                if arrayed && matches!(dim, crate::ImageDimension::D3) {
876                    return Err(TypeError::UnsupportedImageType {
877                        dim,
878                        arrayed,
879                        class,
880                    });
881                }
882                if arrayed && matches!(dim, crate::ImageDimension::Cube) {
883                    self.require_type_capability(Capabilities::CUBE_ARRAY_TEXTURES)?;
884                }
885                if matches!(class, crate::ImageClass::External) {
886                    if dim != crate::ImageDimension::D2 || arrayed {
887                        return Err(TypeError::UnsupportedImageType {
888                            dim,
889                            arrayed,
890                            class,
891                        });
892                    }
893                    self.require_type_capability(Capabilities::TEXTURE_EXTERNAL)?;
894                }
895                TypeInfo::new(
896                    TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
897                    Alignment::ONE,
898                )
899            }
900            Ti::Sampler { .. } => TypeInfo::new(
901                TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
902                Alignment::ONE,
903            ),
904            Ti::AccelerationStructure { vertex_return } => {
905                self.require_type_capability(Capabilities::RAY_TRACING_PIPELINE)
906                    .or_else(|_| self.require_type_capability(Capabilities::RAY_QUERY))?;
907                if vertex_return {
908                    self.require_type_capability(Capabilities::RAY_HIT_VERTEX_POSITION)?;
909                }
910                TypeInfo::new(
911                    TypeFlags::ARGUMENT | TypeFlags::CREATION_RESOLVED,
912                    Alignment::ONE,
913                )
914            }
915            Ti::RayQuery { vertex_return } => {
916                self.require_type_capability(Capabilities::RAY_QUERY)?;
917                if vertex_return {
918                    self.require_type_capability(Capabilities::RAY_HIT_VERTEX_POSITION)?;
919                }
920                TypeInfo::new(
921                    TypeFlags::DATA
922                        | TypeFlags::CONSTRUCTIBLE
923                        | TypeFlags::SIZED
924                        | TypeFlags::CREATION_RESOLVED,
925                    Alignment::ONE,
926                )
927            }
928            Ti::BindingArray { base, size } => {
929                let type_info_mask = match size {
930                    crate::ArraySize::Constant(_) => {
931                        TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED
932                    }
933                    crate::ArraySize::Pending(_) => TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE,
934                    crate::ArraySize::Dynamic => {
935                        // Final type is non-sized
936                        TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED
937                    }
938                };
939                let base_info = &self.types[base.index()];
940
941                if base_info.flags.contains(TypeFlags::DATA) {
942                    // Currently Naga only supports binding arrays of structs for non-handle types.
943                    // `validate_global_var` relies on ray queries (which are `DATA`) being rejected here
944                    match gctx.types[base].inner {
945                        crate::TypeInner::Struct { .. } => {}
946                        _ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)),
947                    };
948                }
949                if matches!(
950                    gctx.types[base].inner,
951                    crate::TypeInner::Image {
952                        class: crate::ImageClass::External,
953                        ..
954                    }
955                ) {
956                    // Binding arrays of external textures are not yet supported.
957                    // See <https://github.com/gfx-rs/wgpu/issues/8027>. Note that
958                    // `validate_global_var` relies on this error being raised here.
959                    return Err(TypeError::BindingArrayBaseExternalTextures);
960                }
961
962                if !base_info.flags.contains(TypeFlags::CREATION_RESOLVED) {
963                    return Err(TypeError::InvalidData(base));
964                }
965
966                TypeInfo::new(base_info.flags & type_info_mask, Alignment::ONE)
967            }
968        })
969    }
970}