Skip to content

Commit

Permalink
[naga] Test CallResult and AtomicResult population.
Browse files Browse the repository at this point in the history
Add tests to ensure that validation checks that `CallResult` and
`AtomicResult` expressions actually have their values provided by
`Call` and `Atomic` statements, and not `Emit` statements.
  • Loading branch information
jimblandy authored and teoxoy committed May 28, 2024
1 parent 59cd0e9 commit 89a0ebf
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 0 deletions.
1 change: 1 addition & 0 deletions naga/tests/root.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod example_wgsl;
mod snapshots;
mod spirv_capabilities;
mod validation;
mod wgsl_errors;
230 changes: 230 additions & 0 deletions naga/tests/validation.rs
Original file line number Diff line number Diff line change
@@ -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<naga::valid::ModuleInfo, naga::WithSpan<naga::valid::ValidationError>> {
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<u32>".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<naga::valid::ModuleInfo, naga::WithSpan<naga::valid::ValidationError>> {
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<naga::valid::ModuleInfo, naga::WithSpan<naga::valid::ValidationError>> {
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());
}

0 comments on commit 89a0ebf

Please sign in to comment.