summaryrefslogtreecommitdiffstats
path: root/third_party/rust/naga/src/proc/validator.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/naga/src/proc/validator.rs')
-rw-r--r--third_party/rust/naga/src/proc/validator.rs489
1 files changed, 489 insertions, 0 deletions
diff --git a/third_party/rust/naga/src/proc/validator.rs b/third_party/rust/naga/src/proc/validator.rs
new file mode 100644
index 0000000000..d9d3eac659
--- /dev/null
+++ b/third_party/rust/naga/src/proc/validator.rs
@@ -0,0 +1,489 @@
+use super::typifier::{ResolveContext, ResolveError, Typifier};
+use crate::arena::{Arena, Handle};
+
+const MAX_BIND_GROUPS: u32 = 8;
+const MAX_LOCATIONS: u32 = 64; // using u64 mask
+const MAX_BIND_INDICES: u32 = 64; // using u64 mask
+const MAX_WORKGROUP_SIZE: u32 = 0x4000;
+
+#[derive(Debug)]
+pub struct Validator {
+ //Note: this is a bit tricky: some of the front-ends as well as backends
+ // already have to use the typifier, so the work here is redundant in a way.
+ typifier: Typifier,
+}
+
+#[derive(Clone, Debug, PartialEq, thiserror::Error)]
+pub enum GlobalVariableError {
+ #[error("Usage isn't compatible with the storage class")]
+ InvalidUsage,
+ #[error("Type isn't compatible with the storage class")]
+ InvalidType,
+ #[error("Interpolation is not valid")]
+ InvalidInterpolation,
+ #[error("Storage access {seen:?} exceed the allowed {allowed:?}")]
+ InvalidStorageAccess {
+ allowed: crate::StorageAccess,
+ seen: crate::StorageAccess,
+ },
+ #[error("Binding decoration is missing or not applicable")]
+ InvalidBinding,
+ #[error("Binding is out of range")]
+ OutOfRangeBinding,
+}
+
+#[derive(Clone, Debug, PartialEq, thiserror::Error)]
+pub enum LocalVariableError {
+ #[error("Initializer is not a constant expression")]
+ InitializerConst,
+ #[error("Initializer doesn't match the variable type")]
+ InitializerType,
+}
+
+#[derive(Clone, Debug, PartialEq, thiserror::Error)]
+pub enum FunctionError {
+ #[error(transparent)]
+ Resolve(#[from] ResolveError),
+ #[error("There are instructions after `return`/`break`/`continue`")]
+ InvalidControlFlowExitTail,
+ #[error("Local variable {handle:?} '{name}' is invalid: {error:?}")]
+ LocalVariable {
+ handle: Handle<crate::LocalVariable>,
+ name: String,
+ error: LocalVariableError,
+ },
+}
+
+#[derive(Clone, Debug, PartialEq, thiserror::Error)]
+pub enum EntryPointError {
+ #[error("Early depth test is not applicable")]
+ UnexpectedEarlyDepthTest,
+ #[error("Workgroup size is not applicable")]
+ UnexpectedWorkgroupSize,
+ #[error("Workgroup size is out of range")]
+ OutOfRangeWorkgroupSize,
+ #[error("Global variable {0:?} is used incorrectly as {1:?}")]
+ InvalidGlobalUsage(Handle<crate::GlobalVariable>, crate::GlobalUse),
+ #[error("Bindings for {0:?} conflict with other global variables")]
+ BindingCollision(Handle<crate::GlobalVariable>),
+ #[error("Built-in {0:?} is not applicable to this entry point")]
+ InvalidBuiltIn(crate::BuiltIn),
+ #[error("Interpolation of an integer has to be flat")]
+ InvalidIntegerInterpolation,
+ #[error(transparent)]
+ Function(#[from] FunctionError),
+}
+
+#[derive(Clone, Debug, PartialEq, thiserror::Error)]
+pub enum ValidationError {
+ #[error("The type {0:?} width {1} is not supported")]
+ InvalidTypeWidth(crate::ScalarKind, crate::Bytes),
+ #[error("The type handle {0:?} can not be resolved")]
+ UnresolvedType(Handle<crate::Type>),
+ #[error("The constant {0:?} can not be used for an array size")]
+ InvalidArraySizeConstant(Handle<crate::Constant>),
+ #[error("Global variable {handle:?} '{name}' is invalid: {error:?}")]
+ GlobalVariable {
+ handle: Handle<crate::GlobalVariable>,
+ name: String,
+ error: GlobalVariableError,
+ },
+ #[error("Function {0:?} is invalid: {1:?}")]
+ Function(Handle<crate::Function>, FunctionError),
+ #[error("Entry point {name} at {stage:?} is invalid: {error:?}")]
+ EntryPoint {
+ stage: crate::ShaderStage,
+ name: String,
+ error: EntryPointError,
+ },
+ #[error("Module is corrupted")]
+ Corrupted,
+}
+
+impl crate::GlobalVariable {
+ fn forbid_interpolation(&self) -> Result<(), GlobalVariableError> {
+ match self.interpolation {
+ Some(_) => Err(GlobalVariableError::InvalidInterpolation),
+ None => Ok(()),
+ }
+ }
+
+ fn check_resource(&self) -> Result<(), GlobalVariableError> {
+ match self.binding {
+ Some(crate::Binding::BuiltIn(_)) => {} // validated per entry point
+ Some(crate::Binding::Resource { group, binding }) => {
+ if group > MAX_BIND_GROUPS || binding > MAX_BIND_INDICES {
+ return Err(GlobalVariableError::OutOfRangeBinding);
+ }
+ }
+ Some(crate::Binding::Location(_)) | None => {
+ return Err(GlobalVariableError::InvalidBinding)
+ }
+ }
+ self.forbid_interpolation()
+ }
+}
+
+fn storage_usage(access: crate::StorageAccess) -> crate::GlobalUse {
+ let mut storage_usage = crate::GlobalUse::empty();
+ if access.contains(crate::StorageAccess::LOAD) {
+ storage_usage |= crate::GlobalUse::LOAD;
+ }
+ if access.contains(crate::StorageAccess::STORE) {
+ storage_usage |= crate::GlobalUse::STORE;
+ }
+ storage_usage
+}
+
+impl Validator {
+ /// Construct a new validator instance.
+ pub fn new() -> Self {
+ Validator {
+ typifier: Typifier::new(),
+ }
+ }
+
+ fn validate_global_var(
+ &self,
+ var: &crate::GlobalVariable,
+ types: &Arena<crate::Type>,
+ ) -> Result<(), GlobalVariableError> {
+ log::debug!("var {:?}", var);
+ let allowed_storage_access = match var.class {
+ crate::StorageClass::Function => return Err(GlobalVariableError::InvalidUsage),
+ crate::StorageClass::Input | crate::StorageClass::Output => {
+ match var.binding {
+ Some(crate::Binding::BuiltIn(_)) => {
+ // validated per entry point
+ var.forbid_interpolation()?
+ }
+ Some(crate::Binding::Location(loc)) => {
+ if loc > MAX_LOCATIONS {
+ return Err(GlobalVariableError::OutOfRangeBinding);
+ }
+ match types[var.ty].inner {
+ crate::TypeInner::Scalar { .. }
+ | crate::TypeInner::Vector { .. }
+ | crate::TypeInner::Matrix { .. } => {}
+ _ => return Err(GlobalVariableError::InvalidType),
+ }
+ }
+ Some(crate::Binding::Resource { .. }) => {
+ return Err(GlobalVariableError::InvalidBinding)
+ }
+ None => {
+ match types[var.ty].inner {
+ //TODO: check the member types
+ crate::TypeInner::Struct { members: _ } => {
+ var.forbid_interpolation()?
+ }
+ _ => return Err(GlobalVariableError::InvalidType),
+ }
+ }
+ }
+ crate::StorageAccess::empty()
+ }
+ crate::StorageClass::Storage => {
+ var.check_resource()?;
+ crate::StorageAccess::all()
+ }
+ crate::StorageClass::Uniform => {
+ var.check_resource()?;
+ crate::StorageAccess::empty()
+ }
+ crate::StorageClass::Handle => {
+ var.check_resource()?;
+ match types[var.ty].inner {
+ crate::TypeInner::Image {
+ class: crate::ImageClass::Storage(_),
+ ..
+ } => crate::StorageAccess::all(),
+ _ => crate::StorageAccess::empty(),
+ }
+ }
+ crate::StorageClass::Private | crate::StorageClass::WorkGroup => {
+ if var.binding.is_some() {
+ return Err(GlobalVariableError::InvalidBinding);
+ }
+ var.forbid_interpolation()?;
+ crate::StorageAccess::empty()
+ }
+ crate::StorageClass::PushConstant => {
+ //TODO
+ return Err(GlobalVariableError::InvalidStorageAccess {
+ allowed: crate::StorageAccess::empty(),
+ seen: crate::StorageAccess::empty(),
+ });
+ }
+ };
+
+ if !allowed_storage_access.contains(var.storage_access) {
+ return Err(GlobalVariableError::InvalidStorageAccess {
+ allowed: allowed_storage_access,
+ seen: var.storage_access,
+ });
+ }
+
+ Ok(())
+ }
+
+ fn validate_local_var(
+ &self,
+ var: &crate::LocalVariable,
+ _fun: &crate::Function,
+ _types: &Arena<crate::Type>,
+ ) -> Result<(), LocalVariableError> {
+ log::debug!("var {:?}", var);
+ if let Some(_expr_handle) = var.init {
+ if false {
+ return Err(LocalVariableError::InitializerConst);
+ }
+ }
+ Ok(())
+ }
+
+ fn validate_function(
+ &mut self,
+ fun: &crate::Function,
+ module: &crate::Module,
+ ) -> Result<(), FunctionError> {
+ let resolve_ctx = ResolveContext {
+ constants: &module.constants,
+ global_vars: &module.global_variables,
+ local_vars: &fun.local_variables,
+ functions: &module.functions,
+ arguments: &fun.arguments,
+ };
+ self.typifier
+ .resolve_all(&fun.expressions, &module.types, &resolve_ctx)?;
+
+ for (var_handle, var) in fun.local_variables.iter() {
+ self.validate_local_var(var, fun, &module.types)
+ .map_err(|error| FunctionError::LocalVariable {
+ handle: var_handle,
+ name: var.name.clone().unwrap_or_default(),
+ error,
+ })?;
+ }
+ Ok(())
+ }
+
+ fn validate_entry_point(
+ &mut self,
+ ep: &crate::EntryPoint,
+ stage: crate::ShaderStage,
+ module: &crate::Module,
+ ) -> Result<(), EntryPointError> {
+ if ep.early_depth_test.is_some() && stage != crate::ShaderStage::Fragment {
+ return Err(EntryPointError::UnexpectedEarlyDepthTest);
+ }
+ if stage == crate::ShaderStage::Compute {
+ if ep
+ .workgroup_size
+ .iter()
+ .any(|&s| s == 0 || s > MAX_WORKGROUP_SIZE)
+ {
+ return Err(EntryPointError::OutOfRangeWorkgroupSize);
+ }
+ } else if ep.workgroup_size != [0; 3] {
+ return Err(EntryPointError::UnexpectedWorkgroupSize);
+ }
+
+ let mut bind_group_masks = [0u64; MAX_BIND_GROUPS as usize];
+ let mut location_in_mask = 0u64;
+ let mut location_out_mask = 0u64;
+ for ((var_handle, var), &usage) in module
+ .global_variables
+ .iter()
+ .zip(&ep.function.global_usage)
+ {
+ if usage.is_empty() {
+ continue;
+ }
+
+ if let Some(crate::Binding::Location(_)) = var.binding {
+ match (stage, var.class) {
+ (crate::ShaderStage::Vertex, crate::StorageClass::Output)
+ | (crate::ShaderStage::Fragment, crate::StorageClass::Input) => {
+ match module.types[var.ty].inner.scalar_kind() {
+ Some(crate::ScalarKind::Float) => {}
+ Some(_) if var.interpolation != Some(crate::Interpolation::Flat) => {
+ return Err(EntryPointError::InvalidIntegerInterpolation);
+ }
+ _ => {}
+ }
+ }
+ _ => {}
+ }
+ }
+
+ let allowed_usage = match var.class {
+ crate::StorageClass::Function => unreachable!(),
+ crate::StorageClass::Input => {
+ let mask = match var.binding {
+ Some(crate::Binding::BuiltIn(built_in)) => match (stage, built_in) {
+ (crate::ShaderStage::Vertex, crate::BuiltIn::BaseInstance)
+ | (crate::ShaderStage::Vertex, crate::BuiltIn::BaseVertex)
+ | (crate::ShaderStage::Vertex, crate::BuiltIn::InstanceIndex)
+ | (crate::ShaderStage::Vertex, crate::BuiltIn::VertexIndex)
+ | (crate::ShaderStage::Fragment, crate::BuiltIn::PointSize)
+ | (crate::ShaderStage::Fragment, crate::BuiltIn::FragCoord)
+ | (crate::ShaderStage::Fragment, crate::BuiltIn::FrontFacing)
+ | (crate::ShaderStage::Fragment, crate::BuiltIn::SampleIndex)
+ | (crate::ShaderStage::Compute, crate::BuiltIn::GlobalInvocationId)
+ | (crate::ShaderStage::Compute, crate::BuiltIn::LocalInvocationId)
+ | (crate::ShaderStage::Compute, crate::BuiltIn::LocalInvocationIndex)
+ | (crate::ShaderStage::Compute, crate::BuiltIn::WorkGroupId) => 0,
+ _ => return Err(EntryPointError::InvalidBuiltIn(built_in)),
+ },
+ Some(crate::Binding::Location(loc)) => 1 << loc,
+ Some(crate::Binding::Resource { .. }) => unreachable!(),
+ None => 0,
+ };
+ if location_in_mask & mask != 0 {
+ return Err(EntryPointError::BindingCollision(var_handle));
+ }
+ location_in_mask |= mask;
+ crate::GlobalUse::LOAD
+ }
+ crate::StorageClass::Output => {
+ let mask = match var.binding {
+ Some(crate::Binding::BuiltIn(built_in)) => match (stage, built_in) {
+ (crate::ShaderStage::Vertex, crate::BuiltIn::Position)
+ | (crate::ShaderStage::Vertex, crate::BuiltIn::PointSize)
+ | (crate::ShaderStage::Vertex, crate::BuiltIn::ClipDistance)
+ | (crate::ShaderStage::Fragment, crate::BuiltIn::FragDepth) => 0,
+ _ => return Err(EntryPointError::InvalidBuiltIn(built_in)),
+ },
+ Some(crate::Binding::Location(loc)) => 1 << loc,
+ Some(crate::Binding::Resource { .. }) => unreachable!(),
+ None => 0,
+ };
+ if location_out_mask & mask != 0 {
+ return Err(EntryPointError::BindingCollision(var_handle));
+ }
+ location_out_mask |= mask;
+ crate::GlobalUse::LOAD | crate::GlobalUse::STORE
+ }
+ crate::StorageClass::Uniform => crate::GlobalUse::LOAD,
+ crate::StorageClass::Storage => storage_usage(var.storage_access),
+ crate::StorageClass::Handle => match module.types[var.ty].inner {
+ crate::TypeInner::Image {
+ class: crate::ImageClass::Storage(_),
+ ..
+ } => storage_usage(var.storage_access),
+ _ => crate::GlobalUse::LOAD,
+ },
+ crate::StorageClass::Private | crate::StorageClass::WorkGroup => {
+ crate::GlobalUse::all()
+ }
+ crate::StorageClass::PushConstant => crate::GlobalUse::LOAD,
+ };
+ if !allowed_usage.contains(usage) {
+ log::warn!("\tUsage error for: {:?}", var);
+ log::warn!(
+ "\tAllowed usage: {:?}, requested: {:?}",
+ allowed_usage,
+ usage
+ );
+ return Err(EntryPointError::InvalidGlobalUsage(var_handle, usage));
+ }
+
+ if let Some(crate::Binding::Resource { group, binding }) = var.binding {
+ let mask = 1 << binding;
+ let group_mask = &mut bind_group_masks[group as usize];
+ if *group_mask & mask != 0 {
+ return Err(EntryPointError::BindingCollision(var_handle));
+ }
+ *group_mask |= mask;
+ }
+ }
+
+ self.validate_function(&ep.function, module)?;
+ Ok(())
+ }
+
+ /// Check the given module to be valid.
+ pub fn validate(&mut self, module: &crate::Module) -> Result<(), ValidationError> {
+ // check the types
+ for (handle, ty) in module.types.iter() {
+ use crate::TypeInner as Ti;
+ match ty.inner {
+ Ti::Scalar { kind, width } | Ti::Vector { kind, width, .. } => {
+ let expected = match kind {
+ crate::ScalarKind::Bool => 1,
+ _ => 4,
+ };
+ if width != expected {
+ return Err(ValidationError::InvalidTypeWidth(kind, width));
+ }
+ }
+ Ti::Matrix { width, .. } => {
+ if width != 4 {
+ return Err(ValidationError::InvalidTypeWidth(
+ crate::ScalarKind::Float,
+ width,
+ ));
+ }
+ }
+ Ti::Pointer { base, class: _ } => {
+ if base >= handle {
+ return Err(ValidationError::UnresolvedType(base));
+ }
+ }
+ Ti::Array { base, size, .. } => {
+ if base >= handle {
+ return Err(ValidationError::UnresolvedType(base));
+ }
+ if let crate::ArraySize::Constant(const_handle) = size {
+ let constant = module
+ .constants
+ .try_get(const_handle)
+ .ok_or(ValidationError::Corrupted)?;
+ match constant.inner {
+ crate::ConstantInner::Uint(_) => {}
+ _ => {
+ return Err(ValidationError::InvalidArraySizeConstant(const_handle))
+ }
+ }
+ }
+ }
+ Ti::Struct { ref members } => {
+ //TODO: check that offsets are not intersecting?
+ for member in members {
+ if member.ty >= handle {
+ return Err(ValidationError::UnresolvedType(member.ty));
+ }
+ }
+ }
+ Ti::Image { .. } => {}
+ Ti::Sampler { comparison: _ } => {}
+ }
+ }
+
+ for (var_handle, var) in module.global_variables.iter() {
+ self.validate_global_var(var, &module.types)
+ .map_err(|error| ValidationError::GlobalVariable {
+ handle: var_handle,
+ name: var.name.clone().unwrap_or_default(),
+ error,
+ })?;
+ }
+
+ for (fun_handle, fun) in module.functions.iter() {
+ self.validate_function(fun, module)
+ .map_err(|e| ValidationError::Function(fun_handle, e))?;
+ }
+
+ for (&(stage, ref name), entry_point) in module.entry_points.iter() {
+ self.validate_entry_point(entry_point, stage, module)
+ .map_err(|error| ValidationError::EntryPoint {
+ stage,
+ name: name.to_string(),
+ error,
+ })?;
+ }
+
+ Ok(())
+ }
+}