1use super::Capabilities;
2use crate::{arena::Handle, proc::Alignment};
3
4bitflags::bitflags! {
5 #[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 const DATA = 0x1;
26
27 const SIZED = 0x2;
41
42 const COPY = 0x4;
44
45 const IO_SHAREABLE = 0x8;
53
54 const HOST_SHAREABLE = 0x10;
56
57 const ARGUMENT = 0x40;
59
60 const CONSTRUCTIBLE = 0x80;
68 }
69}
70
71#[derive(Clone, Copy, Debug, thiserror::Error)]
72#[cfg_attr(test, derive(PartialEq))]
73pub enum Disalignment {
74 #[error("The array stride {stride} is not a multiple of the required alignment {alignment}")]
75 ArrayStride { stride: u32, alignment: Alignment },
76 #[error("The struct span {span}, is not a multiple of the required alignment {alignment}")]
77 StructSpan { span: u32, alignment: Alignment },
78 #[error("The struct member[{index}] offset {offset} is not a multiple of the required alignment {alignment}")]
79 MemberOffset {
80 index: u32,
81 offset: u32,
82 alignment: Alignment,
83 },
84 #[error("The struct member[{index}] offset {offset} must be at least {expected}")]
85 MemberOffsetAfterStruct {
86 index: u32,
87 offset: u32,
88 expected: u32,
89 },
90 #[error("The struct member[{index}] is not statically sized")]
91 UnsizedMember { index: u32 },
92 #[error("The type is not host-shareable")]
93 NonHostShareable,
94}
95
96#[derive(Clone, Debug, thiserror::Error)]
97#[cfg_attr(test, derive(PartialEq))]
98pub enum TypeError {
99 #[error("Capability {0:?} is required")]
100 MissingCapability(Capabilities),
101 #[error("The {0:?} scalar width {1} is not supported for an atomic")]
102 InvalidAtomicWidth(crate::ScalarKind, crate::Bytes),
103 #[error("Invalid type for pointer target {0:?}")]
104 InvalidPointerBase(Handle<crate::Type>),
105 #[error("Unsized types like {base:?} must be in the `Storage` address space, not `{space:?}`")]
106 InvalidPointerToUnsized {
107 base: Handle<crate::Type>,
108 space: crate::AddressSpace,
109 },
110 #[error("Expected data type, found {0:?}")]
111 InvalidData(Handle<crate::Type>),
112 #[error("Base type {0:?} for the array is invalid")]
113 InvalidArrayBaseType(Handle<crate::Type>),
114 #[error("Matrix elements must always be floating-point types")]
115 MatrixElementNotFloat,
116 #[error("The constant {0:?} is specialized, and cannot be used as an array size")]
117 UnsupportedSpecializedArrayLength(Handle<crate::Constant>),
118 #[error("{} of dimensionality {dim:?} and class {class:?} are not supported", if *.arrayed {"Arrayed images"} else {"Images"})]
119 UnsupportedImageType {
120 dim: crate::ImageDimension,
121 arrayed: bool,
122 class: crate::ImageClass,
123 },
124 #[error("Array stride {stride} does not match the expected {expected}")]
125 InvalidArrayStride { stride: u32, expected: u32 },
126 #[error("Field '{0}' can't be dynamically-sized, has type {1:?}")]
127 InvalidDynamicArray(String, Handle<crate::Type>),
128 #[error("The base handle {0:?} has to be a struct")]
129 BindingArrayBaseTypeNotStruct(Handle<crate::Type>),
130 #[error("Structure member[{index}] at {offset} overlaps the previous member")]
131 MemberOverlap { index: u32, offset: u32 },
132 #[error(
133 "Structure member[{index}] at {offset} and size {size} crosses the structure boundary of size {span}"
134 )]
135 MemberOutOfBounds {
136 index: u32,
137 offset: u32,
138 size: u32,
139 span: u32,
140 },
141 #[error("Structure types must have at least one member")]
142 EmptyStruct,
143 #[error(transparent)]
144 WidthError(#[from] WidthError),
145}
146
147#[derive(Clone, Debug, thiserror::Error)]
148#[cfg_attr(test, derive(PartialEq))]
149pub enum WidthError {
150 #[error("The {0:?} scalar width {1} is not supported")]
151 Invalid(crate::ScalarKind, crate::Bytes),
152 #[error("Using `{name}` values requires the `naga::valid::Capabilities::{flag}` flag")]
153 MissingCapability {
154 name: &'static str,
155 flag: &'static str,
156 },
157
158 #[error("Abstract types may only appear in constant expressions")]
159 Abstract,
160}
161
162type LayoutCompatibility = Result<Alignment, (Handle<crate::Type>, Disalignment)>;
164
165fn check_member_layout(
166 accum: &mut LayoutCompatibility,
167 member: &crate::StructMember,
168 member_index: u32,
169 member_layout: LayoutCompatibility,
170 parent_handle: Handle<crate::Type>,
171) {
172 *accum = match (*accum, member_layout) {
173 (Ok(cur_alignment), Ok(alignment)) => {
174 if alignment.is_aligned(member.offset) {
175 Ok(cur_alignment.max(alignment))
176 } else {
177 Err((
178 parent_handle,
179 Disalignment::MemberOffset {
180 index: member_index,
181 offset: member.offset,
182 alignment,
183 },
184 ))
185 }
186 }
187 (Err(e), _) | (_, Err(e)) => Err(e),
188 };
189}
190
191const fn ptr_space_argument_flag(space: crate::AddressSpace) -> TypeFlags {
200 use crate::AddressSpace as As;
201 match space {
202 As::Function | As::Private => TypeFlags::ARGUMENT,
203 As::Uniform | As::Storage { .. } | As::Handle | As::PushConstant | As::WorkGroup => {
204 TypeFlags::empty()
205 }
206 }
207}
208
209#[derive(Clone, Debug)]
210pub(super) struct TypeInfo {
211 pub flags: TypeFlags,
212 pub uniform_layout: LayoutCompatibility,
213 pub storage_layout: LayoutCompatibility,
214}
215
216impl TypeInfo {
217 const fn dummy() -> Self {
218 TypeInfo {
219 flags: TypeFlags::empty(),
220 uniform_layout: Ok(Alignment::ONE),
221 storage_layout: Ok(Alignment::ONE),
222 }
223 }
224
225 const fn new(flags: TypeFlags, alignment: Alignment) -> Self {
226 TypeInfo {
227 flags,
228 uniform_layout: Ok(alignment),
229 storage_layout: Ok(alignment),
230 }
231 }
232}
233
234impl super::Validator {
235 const fn require_type_capability(&self, capability: Capabilities) -> Result<(), TypeError> {
236 if self.capabilities.contains(capability) {
237 Ok(())
238 } else {
239 Err(TypeError::MissingCapability(capability))
240 }
241 }
242
243 pub(super) const fn check_width(&self, scalar: crate::Scalar) -> Result<(), WidthError> {
244 let good = match scalar.kind {
245 crate::ScalarKind::Bool => scalar.width == crate::BOOL_WIDTH,
246 crate::ScalarKind::Float => {
247 if scalar.width == 8 {
248 if !self.capabilities.contains(Capabilities::FLOAT64) {
249 return Err(WidthError::MissingCapability {
250 name: "f64",
251 flag: "FLOAT64",
252 });
253 }
254 true
255 } else {
256 scalar.width == 4
257 }
258 }
259 crate::ScalarKind::Sint => {
260 if scalar.width == 8 {
261 if !self.capabilities.contains(Capabilities::SHADER_INT64) {
262 return Err(WidthError::MissingCapability {
263 name: "i64",
264 flag: "SHADER_INT64",
265 });
266 }
267 true
268 } else {
269 scalar.width == 4
270 }
271 }
272 crate::ScalarKind::Uint => {
273 if scalar.width == 8 {
274 if !self.capabilities.contains(Capabilities::SHADER_INT64) {
275 return Err(WidthError::MissingCapability {
276 name: "u64",
277 flag: "SHADER_INT64",
278 });
279 }
280 true
281 } else {
282 scalar.width == 4
283 }
284 }
285 crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => {
286 return Err(WidthError::Abstract);
287 }
288 };
289 if good {
290 Ok(())
291 } else {
292 Err(WidthError::Invalid(scalar.kind, scalar.width))
293 }
294 }
295
296 pub(super) fn reset_types(&mut self, size: usize) {
297 self.types.clear();
298 self.types.resize(size, TypeInfo::dummy());
299 self.layouter.clear();
300 }
301
302 pub(super) fn validate_type(
303 &self,
304 handle: Handle<crate::Type>,
305 gctx: crate::proc::GlobalCtx,
306 ) -> Result<TypeInfo, TypeError> {
307 use crate::TypeInner as Ti;
308 Ok(match gctx.types[handle].inner {
309 Ti::Scalar(scalar) => {
310 self.check_width(scalar)?;
311 let shareable = if scalar.kind.is_numeric() {
312 TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
313 } else {
314 TypeFlags::empty()
315 };
316 TypeInfo::new(
317 TypeFlags::DATA
318 | TypeFlags::SIZED
319 | TypeFlags::COPY
320 | TypeFlags::ARGUMENT
321 | TypeFlags::CONSTRUCTIBLE
322 | shareable,
323 Alignment::from_width(scalar.width),
324 )
325 }
326 Ti::Vector { size, scalar } => {
327 self.check_width(scalar)?;
328 let shareable = if scalar.kind.is_numeric() {
329 TypeFlags::IO_SHAREABLE | TypeFlags::HOST_SHAREABLE
330 } else {
331 TypeFlags::empty()
332 };
333 TypeInfo::new(
334 TypeFlags::DATA
335 | TypeFlags::SIZED
336 | TypeFlags::COPY
337 | TypeFlags::ARGUMENT
338 | TypeFlags::CONSTRUCTIBLE
339 | shareable,
340 Alignment::from(size) * Alignment::from_width(scalar.width),
341 )
342 }
343 Ti::Matrix {
344 columns: _,
345 rows,
346 scalar,
347 } => {
348 if scalar.kind != crate::ScalarKind::Float {
349 return Err(TypeError::MatrixElementNotFloat);
350 }
351 self.check_width(scalar)?;
352 TypeInfo::new(
353 TypeFlags::DATA
354 | TypeFlags::SIZED
355 | TypeFlags::COPY
356 | TypeFlags::HOST_SHAREABLE
357 | TypeFlags::ARGUMENT
358 | TypeFlags::CONSTRUCTIBLE,
359 Alignment::from(rows) * Alignment::from_width(scalar.width),
360 )
361 }
362 Ti::Atomic(crate::Scalar { kind, width }) => {
363 match kind {
364 crate::ScalarKind::Bool
365 | crate::ScalarKind::Float
366 | crate::ScalarKind::AbstractInt
367 | crate::ScalarKind::AbstractFloat => {
368 return Err(TypeError::InvalidAtomicWidth(kind, width))
369 }
370 crate::ScalarKind::Sint | crate::ScalarKind::Uint => {
371 if width == 8 {
372 if !self.capabilities.intersects(
373 Capabilities::SHADER_INT64_ATOMIC_ALL_OPS
374 | Capabilities::SHADER_INT64_ATOMIC_MIN_MAX,
375 ) {
376 return Err(TypeError::MissingCapability(
377 Capabilities::SHADER_INT64_ATOMIC_ALL_OPS,
378 ));
379 }
380 } else if width != 4 {
381 return Err(TypeError::InvalidAtomicWidth(kind, width));
382 }
383 }
384 };
385 TypeInfo::new(
386 TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE,
387 Alignment::from_width(width),
388 )
389 }
390 Ti::Pointer { base, space } => {
391 use crate::AddressSpace as As;
392
393 let base_info = &self.types[base.index()];
394 if !base_info.flags.contains(TypeFlags::DATA) {
395 return Err(TypeError::InvalidPointerBase(base));
396 }
397
398 if !base_info.flags.contains(TypeFlags::SIZED) {
410 match space {
411 As::Storage { .. } => {}
412 _ => {
413 return Err(TypeError::InvalidPointerToUnsized { base, space });
414 }
415 }
416 }
417
418 let argument_flag = ptr_space_argument_flag(space);
423
424 TypeInfo::new(
427 argument_flag | TypeFlags::SIZED | TypeFlags::COPY,
428 Alignment::ONE,
429 )
430 }
431 Ti::ValuePointer {
432 size: _,
433 scalar,
434 space,
435 } => {
436 self.check_width(scalar)?;
444
445 let argument_flag = ptr_space_argument_flag(space);
450
451 TypeInfo::new(
454 argument_flag | TypeFlags::SIZED | TypeFlags::COPY,
455 Alignment::ONE,
456 )
457 }
458 Ti::Array { base, size, stride } => {
459 let base_info = &self.types[base.index()];
460 if !base_info.flags.contains(TypeFlags::DATA | TypeFlags::SIZED) {
461 return Err(TypeError::InvalidArrayBaseType(base));
462 }
463
464 let base_layout = self.layouter[base];
465 let general_alignment = base_layout.alignment;
466 let uniform_layout = match base_info.uniform_layout {
467 Ok(base_alignment) => {
468 let alignment = base_alignment
469 .max(general_alignment)
470 .max(Alignment::MIN_UNIFORM);
471 if alignment.is_aligned(stride) {
472 Ok(alignment)
473 } else {
474 Err((handle, Disalignment::ArrayStride { stride, alignment }))
475 }
476 }
477 Err(e) => Err(e),
478 };
479 let storage_layout = match base_info.storage_layout {
480 Ok(base_alignment) => {
481 let alignment = base_alignment.max(general_alignment);
482 if alignment.is_aligned(stride) {
483 Ok(alignment)
484 } else {
485 Err((handle, Disalignment::ArrayStride { stride, alignment }))
486 }
487 }
488 Err(e) => Err(e),
489 };
490
491 let type_info_mask = match size {
492 crate::ArraySize::Constant(_) => {
493 TypeFlags::DATA
494 | TypeFlags::SIZED
495 | TypeFlags::COPY
496 | TypeFlags::HOST_SHAREABLE
497 | TypeFlags::ARGUMENT
498 | TypeFlags::CONSTRUCTIBLE
499 }
500 crate::ArraySize::Dynamic => {
501 TypeFlags::DATA | TypeFlags::COPY | TypeFlags::HOST_SHAREABLE
505 }
506 };
507
508 TypeInfo {
509 flags: base_info.flags & type_info_mask,
510 uniform_layout,
511 storage_layout,
512 }
513 }
514 Ti::Struct { ref members, span } => {
515 if members.is_empty() {
516 return Err(TypeError::EmptyStruct);
517 }
518
519 let mut ti = TypeInfo::new(
520 TypeFlags::DATA
521 | TypeFlags::SIZED
522 | TypeFlags::COPY
523 | TypeFlags::HOST_SHAREABLE
524 | TypeFlags::IO_SHAREABLE
525 | TypeFlags::ARGUMENT
526 | TypeFlags::CONSTRUCTIBLE,
527 Alignment::ONE,
528 );
529 ti.uniform_layout = Ok(Alignment::MIN_UNIFORM);
530
531 let mut min_offset = 0;
532 let mut prev_struct_data: Option<(u32, u32)> = None;
533
534 for (i, member) in members.iter().enumerate() {
535 let base_info = &self.types[member.ty.index()];
536 if !base_info.flags.contains(TypeFlags::DATA) {
537 return Err(TypeError::InvalidData(member.ty));
538 }
539 if !base_info.flags.contains(TypeFlags::HOST_SHAREABLE) {
540 if ti.uniform_layout.is_ok() {
541 ti.uniform_layout = Err((member.ty, Disalignment::NonHostShareable));
542 }
543 if ti.storage_layout.is_ok() {
544 ti.storage_layout = Err((member.ty, Disalignment::NonHostShareable));
545 }
546 }
547 ti.flags &= base_info.flags;
548
549 if member.offset < min_offset {
550 if member.offset == 0 {
554 ti.flags.set(TypeFlags::HOST_SHAREABLE, false);
555 } else {
556 return Err(TypeError::MemberOverlap {
557 index: i as u32,
558 offset: member.offset,
559 });
560 }
561 }
562
563 let base_size = gctx.types[member.ty].inner.size(gctx);
564 min_offset = member.offset + base_size;
565 if min_offset > span {
566 return Err(TypeError::MemberOutOfBounds {
567 index: i as u32,
568 offset: member.offset,
569 size: base_size,
570 span,
571 });
572 }
573
574 check_member_layout(
575 &mut ti.uniform_layout,
576 member,
577 i as u32,
578 base_info.uniform_layout,
579 handle,
580 );
581 check_member_layout(
582 &mut ti.storage_layout,
583 member,
584 i as u32,
585 base_info.storage_layout,
586 handle,
587 );
588
589 if let Some((span, offset)) = prev_struct_data {
593 let diff = member.offset - offset;
594 let min = Alignment::MIN_UNIFORM.round_up(span);
595 if diff < min {
596 ti.uniform_layout = Err((
597 handle,
598 Disalignment::MemberOffsetAfterStruct {
599 index: i as u32,
600 offset: member.offset,
601 expected: offset + min,
602 },
603 ));
604 }
605 };
606
607 prev_struct_data = match gctx.types[member.ty].inner {
608 crate::TypeInner::Struct { span, .. } => Some((span, member.offset)),
609 _ => None,
610 };
611
612 if !base_info.flags.contains(TypeFlags::SIZED) {
614 let is_array = match gctx.types[member.ty].inner {
615 crate::TypeInner::Array { .. } => true,
616 _ => false,
617 };
618 if !is_array || i + 1 != members.len() {
619 let name = member.name.clone().unwrap_or_default();
620 return Err(TypeError::InvalidDynamicArray(name, member.ty));
621 }
622 if ti.uniform_layout.is_ok() {
623 ti.uniform_layout =
624 Err((handle, Disalignment::UnsizedMember { index: i as u32 }));
625 }
626 }
627 }
628
629 let alignment = self.layouter[handle].alignment;
630 if !alignment.is_aligned(span) {
631 ti.uniform_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
632 ti.storage_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
633 }
634
635 ti
636 }
637 Ti::Image {
638 dim,
639 arrayed,
640 class,
641 } => {
642 if arrayed && matches!(dim, crate::ImageDimension::D3) {
643 return Err(TypeError::UnsupportedImageType {
644 dim,
645 arrayed,
646 class,
647 });
648 }
649 if arrayed && matches!(dim, crate::ImageDimension::Cube) {
650 self.require_type_capability(Capabilities::CUBE_ARRAY_TEXTURES)?;
651 }
652 TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE)
653 }
654 Ti::Sampler { .. } => TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE),
655 Ti::AccelerationStructure => {
656 self.require_type_capability(Capabilities::RAY_QUERY)?;
657 TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE)
658 }
659 Ti::RayQuery => {
660 self.require_type_capability(Capabilities::RAY_QUERY)?;
661 TypeInfo::new(
662 TypeFlags::DATA | TypeFlags::CONSTRUCTIBLE | TypeFlags::SIZED,
663 Alignment::ONE,
664 )
665 }
666 Ti::BindingArray { base, size } => {
667 let type_info_mask = match size {
668 crate::ArraySize::Constant(_) => TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE,
669 crate::ArraySize::Dynamic => {
670 TypeFlags::HOST_SHAREABLE
672 }
673 };
674 let base_info = &self.types[base.index()];
675
676 if base_info.flags.contains(TypeFlags::DATA) {
677 match gctx.types[base].inner {
679 crate::TypeInner::Struct { .. } => {}
680 _ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)),
681 };
682 }
683
684 TypeInfo::new(base_info.flags & type_info_mask, Alignment::ONE)
685 }
686 })
687 }
688}