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