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;
22
23 const SIZED = 0x2;
37
38 const COPY = 0x4;
40
41 const IO_SHAREABLE = 0x8;
47
48 const HOST_SHAREABLE = 0x10;
50
51 const ARGUMENT = 0x40;
53
54 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
131type 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
160const 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 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 let argument_flag = ptr_space_argument_flag(space);
344
345 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 self.check_width(kind, width)?;
366
367 let argument_flag = ptr_space_argument_flag(space);
372
373 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 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 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 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 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 TypeFlags::HOST_SHAREABLE
580 }
581 };
582 let base_info = &self.types[base.index()];
583
584 if base_info.flags.contains(TypeFlags::DATA) {
585 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}