Files
wgpu/src/valid/type.rs
Dzmitry Malyshau 8cb09c24c5 Atomics in the IR
2021-08-10 21:48:02 -04:00

496 lines
20 KiB
Rust

use super::Capabilities;
use crate::{
arena::{Arena, Handle},
proc::Alignment,
};
bitflags::bitflags! {
/// Flags associated with [`Type`]s by [`Validator`].
///
/// [`Type`]: crate::Type
/// [`Validator`]: crate::valid::Validator
#[repr(transparent)]
pub struct TypeFlags: u8 {
/// Can be used for data variables.
///
/// This flag is required on types of local variables, function
/// arguments, array elements, and struct members.
///
/// This includes all types except `Image`, `Sampler`, `ValuePointer`,
/// and some `Pointer` types.
const DATA = 0x1;
/// The data type has a size known by pipeline creation time.
///
/// Unsized types are quite restricted. The only unsized types permitted
/// by Naga, other than the non-[`DATA`] types like [`Image`] and
/// [`Sampler`], are dynamically-sized [`Array`s], and [`Struct`s] whose
/// last members are such arrays. See the documentation for those types
/// for details.
///
/// [`DATA`]: TypeFlags::DATA
/// [`Image`]: crate::Type::Image
/// [`Sampler`]: crate::Type::Sampler
/// [`Array`]: crate::Type::Array
/// [`Struct`]: crate::Type::struct
const SIZED = 0x2;
/// The data can be copied around.
const COPY = 0x4;
/// Can be be used for interfacing between pipeline stages.
///
/// This includes non-bool scalars and vectors, matrices, and structs
/// and arrays containing only interface types.
const INTERFACE = 0x8;
/// Can be used for host-shareable structures.
const HOST_SHARED = 0x10;
/// This is a top-level host-shareable type.
const TOP_LEVEL = 0x20;
/// This type can be passed as a function argument.
const ARGUMENT = 0x40;
}
}
#[derive(Clone, Copy, Debug, thiserror::Error)]
pub enum Disalignment {
#[error("The array stride {stride} is not a multiple of the required alignment {alignment}")]
ArrayStride { stride: u32, alignment: u32 },
#[error("The struct span {span}, is not a multiple of the required alignment {alignment}")]
StructSpan { span: u32, alignment: u32 },
#[error("The struct member[{index}] offset {offset} is not a multiple of the required alignment {alignment}")]
MemberOffset {
index: u32,
offset: u32,
alignment: u32,
},
#[error("The struct member[{index}] is not statically sized")]
UnsizedMember { index: u32 },
}
#[derive(Clone, Debug, thiserror::Error)]
pub enum TypeError {
#[error("The {0:?} scalar width {1} is not supported")]
InvalidWidth(crate::ScalarKind, crate::Bytes),
#[error("The {0:?} scalar width {1} is not supported for an atomic")]
InvalidAtomicWidth(crate::ScalarKind, crate::Bytes),
#[error("The base handle {0:?} can not be resolved")]
UnresolvedBase(Handle<crate::Type>),
#[error("Invalid type for pointer target {0:?}")]
InvalidPointerBase(Handle<crate::Type>),
#[error("Expected data type, found {0:?}")]
InvalidData(Handle<crate::Type>),
#[error("Structure type {0:?} can not be a block structure")]
InvalidBlockType(Handle<crate::Type>),
#[error("Base type {0:?} for the array is invalid")]
InvalidArrayBaseType(Handle<crate::Type>),
#[error("The constant {0:?} can not be used for an array size")]
InvalidArraySizeConstant(Handle<crate::Constant>),
#[error("Array type {0:?} must have a length of one or more")]
NonPositiveArrayLength(Handle<crate::Constant>),
#[error("Array stride {stride} is smaller than the base element size {base_size}")]
InsufficientArrayStride { stride: u32, base_size: u32 },
#[error("Field '{0}' can't be dynamically-sized, has type {1:?}")]
InvalidDynamicArray(String, Handle<crate::Type>),
#[error("Structure member[{index}] at {offset} overlaps the previous member")]
MemberOverlap { index: u32, offset: u32 },
#[error(
"Structure member[{index}] at {offset} and size {size} crosses the structure boundary of size {span}"
)]
MemberOutOfBounds {
index: u32,
offset: u32,
size: u32,
span: u32,
},
#[error("The composite type contains a top-level structure")]
NestedTopLevel,
}
// Only makes sense if `flags.contains(HOST_SHARED)`
type LayoutCompatibility = Result<Option<Alignment>, (Handle<crate::Type>, Disalignment)>;
fn check_member_layout(
accum: &mut LayoutCompatibility,
member: &crate::StructMember,
member_index: u32,
member_layout: LayoutCompatibility,
parent_handle: Handle<crate::Type>,
) {
*accum = match (*accum, member_layout) {
(Ok(cur_alignment), Ok(align)) => {
let align = align.unwrap().get();
if member.offset % align != 0 {
Err((
parent_handle,
Disalignment::MemberOffset {
index: member_index,
offset: member.offset,
alignment: align,
},
))
} else {
let combined_alignment = ((cur_alignment.unwrap().get() - 1) | (align - 1)) + 1;
Ok(Alignment::new(combined_alignment))
}
}
(Err(e), _) | (_, Err(e)) => Err(e),
};
}
#[derive(Clone, Debug)]
pub(super) struct TypeInfo {
pub flags: TypeFlags,
pub uniform_layout: LayoutCompatibility,
pub storage_layout: LayoutCompatibility,
}
impl TypeInfo {
fn dummy() -> Self {
TypeInfo {
flags: TypeFlags::empty(),
uniform_layout: Ok(None),
storage_layout: Ok(None),
}
}
fn new(flags: TypeFlags, align: u32) -> Self {
let alignment = Alignment::new(align);
TypeInfo {
flags,
uniform_layout: Ok(alignment),
storage_layout: Ok(alignment),
}
}
}
impl super::Validator {
pub(super) fn check_width(&self, kind: crate::ScalarKind, width: crate::Bytes) -> bool {
match kind {
crate::ScalarKind::Bool => width == crate::BOOL_WIDTH,
crate::ScalarKind::Float => {
width == 4 || (width == 8 && self.capabilities.contains(Capabilities::FLOAT64))
}
crate::ScalarKind::Sint | crate::ScalarKind::Uint => width == 4,
}
}
pub(super) fn reset_types(&mut self, size: usize) {
self.types.clear();
self.types.resize(size, TypeInfo::dummy());
self.layouter.clear();
}
pub(super) fn validate_type(
&self,
handle: Handle<crate::Type>,
types: &Arena<crate::Type>,
constants: &Arena<crate::Constant>,
) -> Result<TypeInfo, TypeError> {
use crate::TypeInner as Ti;
Ok(match types[handle].inner {
Ti::Scalar { kind, width } => {
if !self.check_width(kind, width) {
return Err(TypeError::InvalidWidth(kind, width));
}
TypeInfo::new(
TypeFlags::DATA
| TypeFlags::SIZED
| TypeFlags::COPY
| TypeFlags::INTERFACE
| TypeFlags::HOST_SHARED
| TypeFlags::ARGUMENT,
width as u32,
)
}
Ti::Vector { size, kind, width } => {
if !self.check_width(kind, width) {
return Err(TypeError::InvalidWidth(kind, width));
}
let count = if size >= crate::VectorSize::Tri { 4 } else { 2 };
TypeInfo::new(
TypeFlags::DATA
| TypeFlags::SIZED
| TypeFlags::COPY
| TypeFlags::INTERFACE
| TypeFlags::HOST_SHARED
| TypeFlags::ARGUMENT,
count * (width as u32),
)
}
Ti::Matrix {
columns: _,
rows,
width,
} => {
if !self.check_width(crate::ScalarKind::Float, width) {
return Err(TypeError::InvalidWidth(crate::ScalarKind::Float, width));
}
let count = if rows >= crate::VectorSize::Tri { 4 } else { 2 };
TypeInfo::new(
TypeFlags::DATA
| TypeFlags::SIZED
| TypeFlags::COPY
| TypeFlags::INTERFACE
| TypeFlags::HOST_SHARED
| TypeFlags::ARGUMENT,
count * (width as u32),
)
}
Ti::Atomic { kind, width } => {
let good = match kind {
crate::ScalarKind::Bool | crate::ScalarKind::Float => false,
crate::ScalarKind::Sint | crate::ScalarKind::Uint => width == 4,
};
if !good {
return Err(TypeError::InvalidAtomicWidth(kind, width));
}
TypeInfo::new(
TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::HOST_SHARED,
width as u32,
)
}
Ti::Pointer { base, class: _ } => {
if base >= handle {
return Err(TypeError::UnresolvedBase(base));
}
// Pointers to dynamically-sized arrays are needed, to serve as
// the type of an `AccessIndex` expression referring to a
// dynamically sized array appearing as the final member of a
// top-level `Struct`. But such pointers cannot be passed to
// functions, stored in variables, etc. So, we mark them as not
// `DATA`.
let base_info = &self.types[base.index()];
let data_flag = if base_info.flags.contains(TypeFlags::SIZED) {
TypeFlags::DATA | TypeFlags::ARGUMENT
} else if let crate::TypeInner::Struct { .. } = types[base].inner {
TypeFlags::DATA | TypeFlags::ARGUMENT
} else {
TypeFlags::empty()
};
TypeInfo::new(data_flag | TypeFlags::SIZED | TypeFlags::COPY, 0)
}
Ti::ValuePointer {
size: _,
kind,
width,
class: _,
} => {
if !self.check_width(kind, width) {
return Err(TypeError::InvalidWidth(kind, width));
}
TypeInfo::new(TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::COPY, 0)
}
Ti::Array { base, size, stride } => {
if base >= handle {
return Err(TypeError::UnresolvedBase(base));
}
let base_info = &self.types[base.index()];
if !base_info.flags.contains(TypeFlags::DATA | TypeFlags::SIZED) {
return Err(TypeError::InvalidArrayBaseType(base));
}
if base_info.flags.contains(TypeFlags::TOP_LEVEL) {
return Err(TypeError::NestedTopLevel);
}
let base_size = types[base].inner.span(constants);
if stride < base_size {
return Err(TypeError::InsufficientArrayStride { stride, base_size });
}
let general_alignment = self.layouter[base].alignment;
let uniform_layout = match base_info.uniform_layout {
Ok(base_alignment) => {
// combine the alignment requirements
let align = ((base_alignment.unwrap().get() - 1)
| (general_alignment.get() - 1))
+ 1;
if stride % align != 0 {
Err((
handle,
Disalignment::ArrayStride {
stride,
alignment: align,
},
))
} else {
Ok(Alignment::new(align))
}
}
Err(e) => Err(e),
};
let storage_layout = match base_info.storage_layout {
Ok(base_alignment) => {
let align = ((base_alignment.unwrap().get() - 1)
| (general_alignment.get() - 1))
+ 1;
if stride % align != 0 {
Err((
handle,
Disalignment::ArrayStride {
stride,
alignment: align,
},
))
} else {
Ok(Alignment::new(align))
}
}
Err(e) => Err(e),
};
let sized_flag = match size {
crate::ArraySize::Constant(const_handle) => {
let length_is_positive = match constants.try_get(const_handle) {
Some(&crate::Constant {
inner:
crate::ConstantInner::Scalar {
width: _,
value: crate::ScalarValue::Uint(length),
},
..
}) => length > 0,
// Accept a signed integer size to avoid
// requiring an explicit uint
// literal. Type inference should make
// this unnecessary.
Some(&crate::Constant {
inner:
crate::ConstantInner::Scalar {
width: _,
value: crate::ScalarValue::Sint(length),
},
..
}) => length > 0,
other => {
log::warn!("Array size {:?}", other);
return Err(TypeError::InvalidArraySizeConstant(const_handle));
}
};
if !length_is_positive {
return Err(TypeError::NonPositiveArrayLength(const_handle));
}
TypeFlags::SIZED | TypeFlags::ARGUMENT
}
crate::ArraySize::Dynamic => {
// Non-SIZED types may only appear as the last element of a structure.
// This is enforced by checks for SIZED-ness for all compound types,
// and a special case for structs.
TypeFlags::empty()
}
};
let base_mask = TypeFlags::COPY | TypeFlags::HOST_SHARED | TypeFlags::INTERFACE;
TypeInfo {
flags: TypeFlags::DATA | (base_info.flags & base_mask) | sized_flag,
uniform_layout,
storage_layout,
}
}
Ti::Struct {
top_level,
ref members,
span,
} => {
let mut ti = TypeInfo::new(
TypeFlags::DATA
| TypeFlags::SIZED
| TypeFlags::COPY
| TypeFlags::HOST_SHARED
| TypeFlags::INTERFACE
| TypeFlags::ARGUMENT,
1,
);
let mut min_offset = 0;
for (i, member) in members.iter().enumerate() {
if member.ty >= handle {
return Err(TypeError::UnresolvedBase(member.ty));
}
let base_info = &self.types[member.ty.index()];
if !base_info.flags.contains(TypeFlags::DATA) {
return Err(TypeError::InvalidData(member.ty));
}
if top_level && !base_info.flags.contains(TypeFlags::INTERFACE) {
return Err(TypeError::InvalidBlockType(member.ty));
}
if base_info.flags.contains(TypeFlags::TOP_LEVEL) {
return Err(TypeError::NestedTopLevel);
}
ti.flags &= base_info.flags;
if member.offset < min_offset {
//HACK: this could be nicer. We want to allow some structures
// to not bother with offsets/alignments if they are never
// used for host sharing.
if member.offset == 0 {
ti.flags.set(TypeFlags::HOST_SHARED, false);
} else {
return Err(TypeError::MemberOverlap {
index: i as u32,
offset: member.offset,
});
}
}
let base_size = types[member.ty].inner.span(constants);
min_offset = member.offset + base_size;
if min_offset > span {
return Err(TypeError::MemberOutOfBounds {
index: i as u32,
offset: member.offset,
size: base_size,
span,
});
}
check_member_layout(
&mut ti.uniform_layout,
member,
i as u32,
base_info.uniform_layout,
handle,
);
check_member_layout(
&mut ti.storage_layout,
member,
i as u32,
base_info.storage_layout,
handle,
);
// only the last field can be unsized
if !base_info.flags.contains(TypeFlags::SIZED) {
if i + 1 != members.len() {
let name = member.name.clone().unwrap_or_default();
return Err(TypeError::InvalidDynamicArray(name, member.ty));
}
if ti.uniform_layout.is_ok() {
ti.uniform_layout =
Err((handle, Disalignment::UnsizedMember { index: i as u32 }));
}
}
}
if top_level {
ti.flags |= TypeFlags::TOP_LEVEL;
}
let alignment = self.layouter[handle].alignment.get();
if span % alignment != 0 {
ti.uniform_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
ti.storage_layout = Err((handle, Disalignment::StructSpan { span, alignment }));
}
ti
}
Ti::Image { .. } | Ti::Sampler { .. } => TypeInfo::new(TypeFlags::ARGUMENT, 0),
})
}
}