diff --git a/naga/tests/root.rs b/naga/tests/root.rs index fece3ddd7e..208de772d1 100644 --- a/naga/tests/root.rs +++ b/naga/tests/root.rs @@ -1,4 +1,5 @@ mod example_wgsl; mod snapshots; mod spirv_capabilities; +mod validation; mod wgsl_errors; diff --git a/naga/tests/validation.rs b/naga/tests/validation.rs new file mode 100644 index 0000000000..2b632daeb6 --- /dev/null +++ b/naga/tests/validation.rs @@ -0,0 +1,230 @@ +use naga::{valid, Expression, Function, Scalar}; + +#[test] +fn emit_atomic_result() { + use naga::{Module, Type, TypeInner}; + + // We want to ensure that the *only* problem with the code is the + // use of an `Emit` statement instead of an `Atomic` statement. So + // validate two versions of the module varying only in that + // aspect. + // + // Looking at uses of the `atomic` makes it easy to identify the + // differences between the two variants. + fn variant( + atomic: bool, + ) -> Result> { + let span = naga::Span::default(); + let mut module = Module::default(); + let ty_u32 = module.types.insert( + Type { + name: Some("u32".into()), + inner: TypeInner::Scalar(Scalar::U32), + }, + span, + ); + let ty_atomic_u32 = module.types.insert( + Type { + name: Some("atomic".into()), + inner: TypeInner::Atomic(Scalar::U32), + }, + span, + ); + let var_atomic = module.global_variables.append( + naga::GlobalVariable { + name: Some("atomic_global".into()), + space: naga::AddressSpace::WorkGroup, + binding: None, + ty: ty_atomic_u32, + init: None, + }, + span, + ); + + let mut fun = Function::default(); + let ex_global = fun + .expressions + .append(Expression::GlobalVariable(var_atomic), span); + let ex_42 = fun + .expressions + .append(Expression::Literal(naga::Literal::U32(42)), span); + let ex_result = fun.expressions.append( + Expression::AtomicResult { + ty: ty_u32, + comparison: false, + }, + span, + ); + + if atomic { + fun.body.push( + naga::Statement::Atomic { + pointer: ex_global, + fun: naga::AtomicFunction::Add, + value: ex_42, + result: ex_result, + }, + span, + ); + } else { + fun.body.push( + naga::Statement::Emit(naga::Range::new_from_bounds(ex_result, ex_result)), + span, + ); + } + + module.functions.append(fun, span); + + valid::Validator::new( + valid::ValidationFlags::default(), + valid::Capabilities::all(), + ) + .validate(&module) + } + + variant(true).expect("module should validate"); + assert!(variant(false).is_err()); +} + +#[test] +fn emit_call_result() { + use naga::{Module, Type, TypeInner}; + + // We want to ensure that the *only* problem with the code is the + // use of an `Emit` statement instead of a `Call` statement. So + // validate two versions of the module varying only in that + // aspect. + // + // Looking at uses of the `call` makes it easy to identify the + // differences between the two variants. + fn variant( + call: bool, + ) -> Result> { + let span = naga::Span::default(); + let mut module = Module::default(); + let ty_u32 = module.types.insert( + Type { + name: Some("u32".into()), + inner: TypeInner::Scalar(Scalar::U32), + }, + span, + ); + + let mut fun_callee = Function { + result: Some(naga::FunctionResult { + ty: ty_u32, + binding: None, + }), + ..Function::default() + }; + let ex_42 = fun_callee + .expressions + .append(Expression::Literal(naga::Literal::U32(42)), span); + fun_callee + .body + .push(naga::Statement::Return { value: Some(ex_42) }, span); + let fun_callee = module.functions.append(fun_callee, span); + + let mut fun_caller = Function::default(); + let ex_result = fun_caller + .expressions + .append(Expression::CallResult(fun_callee), span); + + if call { + fun_caller.body.push( + naga::Statement::Call { + function: fun_callee, + arguments: vec![], + result: Some(ex_result), + }, + span, + ); + } else { + fun_caller.body.push( + naga::Statement::Emit(naga::Range::new_from_bounds(ex_result, ex_result)), + span, + ); + } + + module.functions.append(fun_caller, span); + + valid::Validator::new( + valid::ValidationFlags::default(), + valid::Capabilities::all(), + ) + .validate(&module) + } + + variant(true).expect("should validate"); + assert!(variant(false).is_err()); +} + +#[test] +fn emit_workgroup_uniform_load_result() { + use naga::{Module, Type, TypeInner}; + + // We want to ensure that the *only* problem with the code is the + // use of an `Emit` statement instead of an `Atomic` statement. So + // validate two versions of the module varying only in that + // aspect. + // + // Looking at uses of the `wg_load` makes it easy to identify the + // differences between the two variants. + fn variant( + wg_load: bool, + ) -> Result> { + let span = naga::Span::default(); + let mut module = Module::default(); + let ty_u32 = module.types.insert( + Type { + name: Some("u32".into()), + inner: TypeInner::Scalar(Scalar::U32), + }, + span, + ); + let var_workgroup = module.global_variables.append( + naga::GlobalVariable { + name: Some("workgroup_global".into()), + space: naga::AddressSpace::WorkGroup, + binding: None, + ty: ty_u32, + init: None, + }, + span, + ); + + let mut fun = Function::default(); + let ex_global = fun + .expressions + .append(Expression::GlobalVariable(var_workgroup), span); + let ex_result = fun + .expressions + .append(Expression::WorkGroupUniformLoadResult { ty: ty_u32 }, span); + + if wg_load { + fun.body.push( + naga::Statement::WorkGroupUniformLoad { + pointer: ex_global, + result: ex_result, + }, + span, + ); + } else { + fun.body.push( + naga::Statement::Emit(naga::Range::new_from_bounds(ex_result, ex_result)), + span, + ); + } + + module.functions.append(fun, span); + + valid::Validator::new( + valid::ValidationFlags::default(), + valid::Capabilities::all(), + ) + .validate(&module) + } + + variant(true).expect("module should validate"); + assert!(variant(false).is_err()); +}