naga/valid/
type.rs

1use super::Capabilities;
2use crate::{arena::Handle, proc::Alignment};
3
4bitflags::bitflags! {
5    /// Flags associated with [`Type`]s by [`Validator`].
6    ///
7    /// [`Type`]: crate::Type
8    /// [`Validator`]: crate::valid::Validator
9    #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
10    #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
11    #[repr(transparent)]
12    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
13    pub struct TypeFlags: u8 {
14        /// Can be used for data variables.
15        ///
16        /// This flag is required on types of local variables, function
17        /// arguments, array elements, and struct members.
18        ///
19        /// This includes all types except `Image`, `Sampler`,
20        /// and some `Pointer` types.
21        const DATA = 0x1;
22
23        /// The data type has a size known by pipeline creation time.
24        ///
25        /// Unsized types are quite restricted. The only unsized types permitted
26        /// by Naga, other than the non-[`DATA`] types like [`Image`] and
27        /// [`Sampler`], are dynamically-sized [`Array`s], and [`Struct`s] whose
28        /// last members are such arrays. See the documentation for those types
29        /// for details.
30        ///
31        /// [`DATA`]: TypeFlags::DATA
32        /// [`Image`]: crate::Type::Image
33        /// [`Sampler`]: crate::Type::Sampler
34        /// [`Array`]: crate::Type::Array
35        /// [`Struct`]: crate::Type::struct
36        const SIZED = 0x2;
37
38        /// The data can be copied around.
39        const COPY = 0x4;
40
41        /// Can be be used for user-defined IO between pipeline stages.
42        ///
43        /// This covers anything that can be in [`Location`] binding:
44        /// non-bool scalars and vectors, matrices, and structs and
45        /// arrays containing only interface types.
46        const IO_SHAREABLE = 0x8;
47
48        /// Can be used for host-shareable structures.
49        const HOST_SHAREABLE = 0x10;
50
51        /// This type can be passed as a function argument.
52        const ARGUMENT = 0x40;
53
54        /// A WGSL [constructible] type.
55        ///
56        /// The constructible types are scalars, vectors, matrices, fixed-size
57        /// arrays of constructible types, and structs whose members are all
58        /// constructible.
59        ///
60        /// [constructible]: https://gpuweb.github.io/gpuweb/wgsl/#constructible
61        const CONSTRUCTIBLE = 0x80;
62    }
63}
64
65#[derive(Clone, Copy, Debug, thiserror::Error)]
66pub enum Disalignment {
67    #[error("The array stride {stride} is not a multiple of the required alignment {alignment}")]
68    ArrayStride { stride: u32, alignment: Alignment },
69    #[error("The struct span {span}, is not a multiple of the required alignment {alignment}")]
70    StructSpan { span: u32, alignment: Alignment },
71    #[error("The struct member[{index}] offset {offset} is not a multiple of the required alignment {alignment}")]
72    MemberOffset {
73        index: u32,
74        offset: u32,
75        alignment: Alignment,
76    },
77    #[error("The struct member[{index}] offset {offset} must be at least {expected}")]
78    MemberOffsetAfterStruct {
79        index: u32,
80        offset: u32,
81        expected: u32,
82    },
83    #[error("The struct member[{index}] is not statically sized")]
84    UnsizedMember { index: u32 },
85    #[error("The type is not host-shareable")]
86    NonHostShareable,
87}
88
89#[derive(Clone, Debug, thiserror::Error)]
90pub enum TypeError {
91    #[error("Capability {0:?} is required")]
92    MissingCapability(Capabilities),
93    #[error("The {0:?} scalar width {1} is not supported")]
94    InvalidWidth(crate::ScalarKind, crate::Bytes),
95    #[error("The {0:?} scalar width {1} is not supported for an atomic")]
96    InvalidAtomicWidth(crate::ScalarKind, crate::Bytes),
97    #[error("Invalid type for pointer target {0:?}")]
98    InvalidPointerBase(Handle<crate::Type>),
99    #[error("Unsized types like {base:?} must be in the `Storage` address space, not `{space:?}`")]
100    InvalidPointerToUnsized {
101        base: Handle<crate::Type>,
102        space: crate::AddressSpace,
103    },
104    #[error("Expected data type, found {0:?}")]
105    InvalidData(Handle<crate::Type>),
106    #[error("Base type {0:?} for the array is invalid")]
107    InvalidArrayBaseType(Handle<crate::Type>),
108    #[error("The constant {0:?} is specialized, and cannot be used as an array size")]
109    UnsupportedSpecializedArrayLength(Handle<crate::Constant>),
110    #[error("Array stride {stride} does not match the expected {expected}")]
111    InvalidArrayStride { stride: u32, expected: u32 },
112    #[error("Field '{0}' can't be dynamically-sized, has type {1:?}")]
113    InvalidDynamicArray(String, Handle<crate::Type>),
114    #[error("The base handle {0:?} has to be a struct")]
115    BindingArrayBaseTypeNotStruct(Handle<crate::Type>),
116    #[error("Structure member[{index}] at {offset} overlaps the previous member")]
117    MemberOverlap { index: u32, offset: u32 },
118    #[error(
119        "Structure member[{index}] at {offset} and size {size} crosses the structure boundary of size {span}"
120    )]
121    MemberOutOfBounds {
122        index: u32,
123        offset: u32,
124        size: u32,
125        span: u32,
126    },
127    #[error("Structure types must have at least one member")]
128    EmptyStruct,
129}
130
131// Only makes sense if `flags.contains(HOST_SHAREABLE)`
132type LayoutCompatibility = Result<Alignment, (Handle<crate::Type>, Disalignment)>;
133
134fn check_member_layout(
135    accum: &mut LayoutCompatibility,
136    member: &crate::StructMember,
137    member_index: u32,
138    member_layout: LayoutCompatibility,
139    parent_handle: Handle<crate::Type>,
140) {
141    *accum = match (*accum, member_layout) {
142        (Ok(cur_alignment), Ok(alignment)) => {
143            if alignment.is_aligned(member.offset) {
144                Ok(cur_alignment.max(alignment))
145            } else {
146                Err((
147                    parent_handle,
148                    Disalignment::MemberOffset {
149                        index: member_index,
150                        offset: member.offset,
151                        alignment,
152                    },
153                ))
154            }
155        }
156        (Err(e), _) | (_, Err(e)) => Err(e),
157    };
158}
159
160/// Determine whether a pointer in `space` can be passed as an argument.
161///
162/// If a pointer in `space` is permitted to be passed as an argument to a
163/// user-defined function, return `TypeFlags::ARGUMENT`. Otherwise, return
164/// `TypeFlags::empty()`.
165///
166/// Pointers passed as arguments to user-defined functions must be in the
167/// `Function`, `Private`, or `Workgroup` storage space.
168const fn ptr_space_argument_flag(space: crate::AddressSpace) -> TypeFlags {
169    use crate::AddressSpace as As;
170    match space {
171        As::Function | As::Private | As::WorkGroup => TypeFlags::ARGUMENT,
172        As::Uniform | As::Storage { .. } | As::Handle | As::PushConstant => TypeFlags::empty(),
173    }
174}
175
176#[derive(Clone, Debug)]
177pub(super) struct TypeInfo {
178    pub flags: TypeFlags,
179    pub uniform_layout: LayoutCompatibility,
180    pub storage_layout: LayoutCompatibility,
181}
182
183impl TypeInfo {
184    const fn dummy() -> Self {
185        TypeInfo {
186            flags: TypeFlags::empty(),
187            uniform_layout: Ok(Alignment::ONE),
188            storage_layout: Ok(Alignment::ONE),
189        }
190    }
191
192    const fn new(flags: TypeFlags, alignment: Alignment) -> Self {
193        TypeInfo {
194            flags,
195            uniform_layout: Ok(alignment),
196            storage_layout: Ok(alignment),
197        }
198    }
199}
200
201impl super::Validator {
202    const fn require_type_capability(&self, capability: Capabilities) -> Result<(), TypeError> {
203        if self.capabilities.contains(capability) {
204            Ok(())
205        } else {
206            Err(TypeError::MissingCapability(capability))
207        }
208    }
209
210    pub(super) fn check_width(
211        &self,
212        kind: crate::ScalarKind,
213        width: crate::Bytes,
214    ) -> Result<(), TypeError> {
215        let good = match kind {
216            crate::ScalarKind::Bool => width == crate::BOOL_WIDTH,
217            crate::ScalarKind::Float => {
218                if width == 8 {
219                    self.require_type_capability(Capabilities::FLOAT64)?;
220                    true
221                } else {
222                    width == 4
223                }
224            }
225            crate::ScalarKind::Sint | crate::ScalarKind::Uint => width == 4,
226        };
227        if good {
228            Ok(())
229        } else {
230            Err(TypeError::InvalidWidth(kind, width))
231        }
232    }
233
234    pub(super) fn reset_types(&mut self, size: usize) {
235        self.types.clear();
236        self.types.resize(size, TypeInfo::dummy());
237        self.layouter.clear();
238    }
239
240    pub(super) fn validate_type(
241        &self,
242        handle: Handle<crate::Type>,
243        gctx: crate::proc::GlobalCtx,
244    ) -> Result<TypeInfo, TypeError> {
245        use crate::TypeInner as Ti;
246        Ok(match gctx.types[handle].inner {
247            Ti::Scalar { kind, width } => {
248                self.check_width(kind, width)?;
249                let shareable = if kind.is_numeric() {
250                    TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
251                } else {
252                    TypeFlags::empty()
253                };
254                TypeInfo::new(
255                    TypeFlags::DATA
256                        | TypeFlags::SIZED
257                        | TypeFlags::COPY
258                        | TypeFlags::ARGUMENT
259                        | TypeFlags::CONSTRUCTIBLE
260                        | shareable,
261                    Alignment::from_width(width),
262                )
263            }
264            Ti::Vector { size, kind, width } => {
265                self.check_width(kind, width)?;
266                let shareable = if kind.is_numeric() {
267                    TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
268                } else {
269                    TypeFlags::empty()
270                };
271                TypeInfo::new(
272                    TypeFlags::DATA
273                        | TypeFlags::SIZED
274                        | TypeFlags::COPY
275                        | TypeFlags::HOST_SHAREABLE
276                        | TypeFlags::ARGUMENT
277                        | TypeFlags::CONSTRUCTIBLE
278                        | shareable,
279                    Alignment::from(size) * Alignment::from_width(width),
280                )
281            }
282            Ti::Matrix {
283                columns: _,
284                rows,
285                width,
286            } => {
287                self.check_width(crate::ScalarKind::Float, width)?;
288                TypeInfo::new(
289                    TypeFlags::DATA
290                        | TypeFlags::SIZED
291                        | TypeFlags::COPY
292                        | TypeFlags::HOST_SHAREABLE
293                        | TypeFlags::ARGUMENT
294                        | TypeFlags::CONSTRUCTIBLE,
295                    Alignment::from(rows) * Alignment::from_width(width),
296                )
297            }
298            Ti::Atomic { kind, width } => {
299                let good = match kind {
300                    crate::ScalarKind::Bool | crate::ScalarKind::Float => false,
301                    crate::ScalarKind::Sint | crate::ScalarKind::Uint => width == 4,
302                };
303                if !good {
304                    return Err(TypeError::InvalidAtomicWidth(kind, width));
305                }
306                TypeInfo::new(
307                    TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE,
308                    Alignment::from_width(width),
309                )
310            }
311            Ti::Pointer { base, space } => {
312                use crate::AddressSpace as As;
313
314                let base_info = &self.types[base.index()];
315                if !base_info.flags.contains(TypeFlags::DATA) {
316                    return Err(TypeError::InvalidPointerBase(base));
317                }
318
319                // Runtime-sized values can only live in the `Storage` storage
320                // space, so it's useless to have a pointer to such a type in
321                // any other space.
322                //
323                // Detecting this problem here prevents the definition of
324                // functions like:
325                //
326                //     fn f(p: ptr<workgroup, UnsizedType>) -> ... { ... }
327                //
328                // which would otherwise be permitted, but uncallable. (They
329                // may also present difficulties in code generation).
330                if !base_info.flags.contains(TypeFlags::SIZED) {
331                    match space {
332                        As::Storage { .. } => {}
333                        _ => {
334                            return Err(TypeError::InvalidPointerToUnsized { base, space });
335                        }
336                    }
337                }
338
339                // `Validator::validate_function` actually checks the storage
340                // space of pointer arguments explicitly before checking the
341                // `ARGUMENT` flag, to give better error messages. But it seems
342                // best to set `ARGUMENT` accurately anyway.
343                let argument_flag = ptr_space_argument_flag(space);
344
345                // Pointers cannot be stored in variables, structure members, or
346                // array elements, so we do not mark them as `DATA`.
347                TypeInfo::new(
348                    argument_flag | TypeFlags::SIZED | TypeFlags::COPY,
349                    Alignment::ONE,
350                )
351            }
352            Ti::ValuePointer {
353                size: _,
354                kind,
355                width,
356                space,
357            } => {
358                // ValuePointer should be treated the same way as the equivalent
359                // Pointer / Scalar / Vector combination, so each step in those
360                // variants' match arms should have a counterpart here.
361                //
362                // However, some cases are trivial: All our implicit base types
363                // are DATA and SIZED, so we can never return
364                // `InvalidPointerBase` or `InvalidPointerToUnsized`.
365                self.check_width(kind, width)?;
366
367                // `Validator::validate_function` actually checks the storage
368                // space of pointer arguments explicitly before checking the
369                // `ARGUMENT` flag, to give better error messages. But it seems
370                // best to set `ARGUMENT` accurately anyway.
371                let argument_flag = ptr_space_argument_flag(space);
372
373                // Pointers cannot be stored in variables, structure members, or
374                // array elements, so we do not mark them as `DATA`.
375                TypeInfo::new(
376                    argument_flag | TypeFlags::SIZED | TypeFlags::COPY,
377                    Alignment::ONE,
378                )
379            }
380            Ti::Array { base, size, stride } => {
381                let base_info = &self.types[base.index()];
382                if !base_info.flags.contains(TypeFlags::DATA | TypeFlags::SIZED) {
383                    return Err(TypeError::InvalidArrayBaseType(base));
384                }
385
386                let base_layout = self.layouter[base];
387                let general_alignment = base_layout.alignment;
388                let uniform_layout = match base_info.uniform_layout {
389                    Ok(base_alignment) => {
390                        let alignment = base_alignment
391                            .max(general_alignment)
392                            .max(Alignment::MIN_UNIFORM);
393                        if alignment.is_aligned(stride) {
394                            Ok(alignment)
395                        } else {
396                            Err((handle, Disalignment::ArrayStride { stride, alignment }))
397                        }
398                    }
399                    Err(e) => Err(e),
400                };
401                let storage_layout = match base_info.storage_layout {
402                    Ok(base_alignment) => {
403                        let alignment = base_alignment.max(general_alignment);
404                        if alignment.is_aligned(stride) {
405                            Ok(alignment)
406                        } else {
407                            Err((handle, Disalignment::ArrayStride { stride, alignment }))
408                        }
409                    }
410                    Err(e) => Err(e),
411                };
412
413                let type_info_mask = match size {
414                    crate::ArraySize::Constant(_) => {
415                        TypeFlags::DATA
416                            | TypeFlags::SIZED
417                            | TypeFlags::COPY
418                            | TypeFlags::HOST_SHAREABLE
419                            | TypeFlags::ARGUMENT
420                            | TypeFlags::CONSTRUCTIBLE
421                    }
422                    crate::ArraySize::Dynamic => {
423                        // Non-SIZED types may only appear as the last element of a structure.
424                        // This is enforced by checks for SIZED-ness for all compound types,
425                        // and a special case for structs.
426                        TypeFlags::DATA | TypeFlags::COPY | TypeFlags::HOST_SHAREABLE
427                    }
428                };
429
430                TypeInfo {
431                    flags: base_info.flags & type_info_mask,
432                    uniform_layout,
433                    storage_layout,
434                }
435            }
436            Ti::Struct { ref members, span } => {
437                if members.is_empty() {
438                    return Err(TypeError::EmptyStruct);
439                }
440
441                let mut ti = TypeInfo::new(
442                    TypeFlags::DATA
443                        | TypeFlags::SIZED
444                        | TypeFlags::COPY
445                        | TypeFlags::HOST_SHAREABLE
446                        | TypeFlags::IO_SHAREABLE
447                        | TypeFlags::ARGUMENT
448                        | TypeFlags::CONSTRUCTIBLE,
449                    Alignment::ONE,
450                );
451                ti.uniform_layout = Ok(Alignment::MIN_UNIFORM);
452
453                let mut min_offset = 0;
454
455                let mut prev_struct_data: Option<(u32, u32)> = None;
456
457                for (i, member) in members.iter().enumerate() {
458                    let base_info = &self.types[member.ty.index()];
459                    if !base_info.flags.contains(TypeFlags::DATA) {
460                        return Err(TypeError::InvalidData(member.ty));
461                    }
462                    if !base_info.flags.contains(TypeFlags::HOST_SHAREABLE) {
463                        if ti.uniform_layout.is_ok() {
464                            ti.uniform_layout = Err((member.ty, Disalignment::NonHostShareable));
465                        }
466                        if ti.storage_layout.is_ok() {
467                            ti.storage_layout = Err((member.ty, Disalignment::NonHostShareable));
468                        }
469                    }
470                    ti.flags &= base_info.flags;
471
472                    if member.offset < min_offset {
473                        // HACK: this could be nicer. We want to allow some structures
474                        // to not bother with offsets/alignments if they are never
475                        // used for host sharing.
476                        if member.offset == 0 {
477                            ti.flags.set(TypeFlags::HOST_SHAREABLE, false);
478                        } else {
479                            return Err(TypeError::MemberOverlap {
480                                index: i as u32,
481                                offset: member.offset,
482                            });
483                        }
484                    }
485
486                    let base_size = gctx.types[member.ty].inner.size(gctx);
487                    min_offset = member.offset + base_size;
488                    if min_offset > span {
489                        return Err(TypeError::MemberOutOfBounds {
490                            index: i as u32,
491                            offset: member.offset,
492                            size: base_size,
493                            span,
494                        });
495                    }
496
497                    check_member_layout(
498                        &mut ti.uniform_layout,
499                        member,
500                        i as u32,
501                        base_info.uniform_layout,
502                        handle,
503                    );
504                    check_member_layout(
505                        &mut ti.storage_layout,
506                        member,
507                        i as u32,
508                        base_info.storage_layout,
509                        handle,
510                    );
511
512                    // Validate rule: If a structure member itself has a structure type S,
513                    // then the number of bytes between the start of that member and
514                    // the start of any following member must be at least roundUp(16, SizeOf(S)).
515                    if let Some((span, offset)) = prev_struct_data {
516                        let diff = member.offset - offset;
517                        let min = Alignment::MIN_UNIFORM.round_up(span);
518                        if diff < min {
519                            ti.uniform_layout = Err((
520                                handle,
521                                Disalignment::MemberOffsetAfterStruct {
522                                    index: i as u32,
523                                    offset: member.offset,
524                                    expected: offset + min,
525                                },
526                            ));
527                        }
528                    };
529
530                    prev_struct_data = match gctx.types[member.ty].inner {
531                        crate::TypeInner::Struct { span, .. } => Some((span, member.offset)),
532                        _ => None,
533                    };
534
535                    // The last field may be an unsized array.
536                    if !base_info.flags.contains(TypeFlags::SIZED) {
537                        let is_array = match gctx.types[member.ty].inner {
538                            crate::TypeInner::Array { .. } => true,
539                            _ => false,
540                        };
541                        if !is_array || i + 1 != members.len() {
542                            let name = member.name.clone().unwrap_or_default();
543                            return Err(TypeError::InvalidDynamicArray(name, member.ty));
544                        }
545                        if ti.uniform_layout.is_ok() {
546                            ti.uniform_layout =
547                                Err((handle, Disalignment::UnsizedMember { index: i as u32 }));
548                        }
549                    }
550                }
551
552                let alignment = self.layouter[handle].alignment;
553                if !alignment.is_aligned(span) {
554                    ti.uniform_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
555                    ti.storage_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
556                }
557
558                ti
559            }
560            Ti::Image { .. } | Ti::Sampler { .. } => {
561                TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE)
562            }
563            Ti::AccelerationStructure => {
564                self.require_type_capability(Capabilities::RAY_QUERY)?;
565                TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE)
566            }
567            Ti::RayQuery => {
568                self.require_type_capability(Capabilities::RAY_QUERY)?;
569                TypeInfo::new(TypeFlags::DATA | TypeFlags::SIZED, Alignment::ONE)
570            }
571            Ti::BindingArray { base, size } => {
572                if base >= handle {
573                    return Err(TypeError::InvalidArrayBaseType(base));
574                }
575                let type_info_mask = match size {
576                    crate::ArraySize::Constant(_) => TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE,
577                    crate::ArraySize::Dynamic => {
578                        // Final type is non-sized
579                        TypeFlags::HOST_SHAREABLE
580                    }
581                };
582                let base_info = &self.types[base.index()];
583
584                if base_info.flags.contains(TypeFlags::DATA) {
585                    // Currently Naga only supports binding arrays of structs for non-handle types.
586                    match gctx.types[base].inner {
587                        crate::TypeInner::Struct { .. } => {}
588                        _ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)),
589                    };
590                }
591
592                TypeInfo::new(base_info.flags & type_info_mask, Alignment::ONE)
593            }
594        })
595    }
596}