Skip to content

Commit

Permalink
Shader validation tests for atomics
Browse files Browse the repository at this point in the history
* type validation tests
  * address space
  * type
  * invalid operations
* builtin function tests
  * parameterizations
  • Loading branch information
alan-baker authored and amaiorano committed Mar 8, 2024
1 parent 0a68bf7 commit e6ef0b8
Show file tree
Hide file tree
Showing 3 changed files with 281 additions and 13 deletions.
4 changes: 4 additions & 0 deletions src/webgpu/listing_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -1833,6 +1833,7 @@
"webgpu:shader,validation,expression,call,builtin,atan:values:*": { "subcaseMS": 0.335 },
"webgpu:shader,validation,expression,call,builtin,atanh:integer_argument:*": { "subcaseMS": 0.912 },
"webgpu:shader,validation,expression,call,builtin,atanh:values:*": { "subcaseMS": 0.231 },
"webgpu:shader,validation,expression,call,builtin,atomics:atomic_parameterization:*": { "subcaseMS": 1.346 },
"webgpu:shader,validation,expression,call,builtin,atomics:stage:*": { "subcaseMS": 1.346 },
"webgpu:shader,validation,expression,call,builtin,barriers:no_return_value:*": { "subcaseMS": 1.500 },
"webgpu:shader,validation,expression,call,builtin,barriers:only_in_compute:*": { "subcaseMS": 1.500 },
Expand Down Expand Up @@ -2138,6 +2139,9 @@
"webgpu:shader,validation,types,alias:no_indirect_recursion_via_struct_attribute:*": { "subcaseMS": 1.584 },
"webgpu:shader,validation,types,alias:no_indirect_recursion_via_struct_member:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,types,alias:no_indirect_recursion_via_vector_element:*": { "subcaseMS": 1.050 },
"webgpu:shader,validation,types,atomics:address_space:*": { "subcaseMS": 1.050 },
"webgpu:shader,validation,types,atomics:invalid_operations:*": { "subcaseMS": 1.050 },
"webgpu:shader,validation,types,atomics:type:*": { "subcaseMS": 1.050 },
"webgpu:shader,validation,types,struct:no_direct_recursion:*": { "subcaseMS": 0.951 },
"webgpu:shader,validation,types,struct:no_indirect_recursion:*": { "subcaseMS": 0.901 },
"webgpu:shader,validation,types,struct:no_indirect_recursion_via_array_element:*": { "subcaseMS": 0.901 },
Expand Down
145 changes: 132 additions & 13 deletions src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,44 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';

export const g = makeTestGroup(ShaderValidationTest);

const kAtomicOps = {
add: { src: 'atomicAdd(&a,1)' },
sub: { src: 'atomicSub(&a,1)' },
max: { src: 'atomicMax(&a,1)' },
min: { src: 'atomicMin(&a,1)' },
and: { src: 'atomicAnd(&a,1)' },
or: { src: 'atomicOr(&a,1)' },
xor: { src: 'atomicXor(&a,1)' },
load: { src: 'atomicLoad(&a)' },
store: { src: 'atomicStore(&a,1)' },
exchange: { src: 'atomicExchange(&a,1)' },
compareexchangeweak: { src: 'atomicCompareExchangeWeak(&a,1,1)' },
interface stringToString {
(a: string): string;
}

const kAtomicOps: Record<string, stringToString> = {
add: (a: string): string => {
return `atomicAdd(${a},1)`;
},
sub: (a: string): string => {
return `atomicSub(${a},1)`;
},
max: (a: string): string => {
return `atomicMax(${a},1)`;
},
min: (a: string): string => {
return `atomicMin(${a},1)`;
},
and: (a: string): string => {
return `atomicAnd(${a},1)`;
},
or: (a: string): string => {
return `atomicOr(${a},1)`;
},
xor: (a: string): string => {
return `atomicXor(${a},1)`;
},
load: (a: string): string => {
return `atomicLoad(${a})`;
},
store: (a: string): string => {
return `atomicStore(${a},1)`;
},
exchange: (a: string): string => {
return `atomicExchange(${a},1)`;
},
compareexchangeweak: (a: string): string => {
return `atomicCompareExchangeWeak(${a},1,1)`;
},
};

g.test('stage')
Expand All @@ -35,7 +61,7 @@ Atomic built-in functions must not be used in a vertex shader stage.
.combine('atomicOp', keysOf(kAtomicOps))
)
.fn(t => {
const atomicOp = kAtomicOps[t.params.atomicOp].src;
const atomicOp = kAtomicOps[t.params.atomicOp](`&a`);
let code = `
@group(0) @binding(0) var<storage, read_write> a: atomic<i32>;
`;
Expand Down Expand Up @@ -68,3 +94,96 @@ Atomic built-in functions must not be used in a vertex shader stage.
const pass = t.params.stage !== 'vertex';
t.expectCompileResult(pass, code);
});

function generateAtomicCode(
type: string,
access: string,
aspace: string,
style: string,
op: string
): string {
let moduleVar = ``;
let functionVar = ``;
let param = ``;
let aParam = ``;
if (style === 'var') {
aParam = `&a`;
switch (aspace) {
case 'storage':
moduleVar = `@group(0) @binding(0) var<storage, ${access}> a : atomic<${type}>;\n`;
break;
case 'workgroup':
moduleVar = `var<workgroup> a : atomic<${type}>;\n`;
break;
case 'uniform':
moduleVar = `@group(0) @binding(0) var<uniform> a : atomic<${type}>;\n`;
break;
case 'private':
moduleVar = `var<private> a : atomic<${type}>;\n`;
break;
case 'function':
functionVar = `var a : atomic<${type}>;\n`;
break;
default:
break;
}
} else {
const aspaceParam = aspace === 'storage' ? `, ${access}` : ``;
param = `p : ptr<${aspace}, atomic<${type}>${aspaceParam}>`;
aParam = `p`;
}

return `
${moduleVar}
fn foo(${param}) {
${functionVar}
${kAtomicOps[op](aParam)};
}
`;
}

g.test('atomic_parameterization')
.desc('Tests the valid atomic parameters')
.params(u =>
u
.combine('op', keysOf(kAtomicOps))
.beginSubcases()
.combine('aspace', ['storage', 'workgroup', 'private', 'uniform', 'function'] as const)
.combine('access', ['read', 'read_write'] as const)
.combine('type', ['i32', 'u32'] as const)
.combine('style', ['param', 'var'] as const)
.filter(t => {
switch (t.aspace) {
case 'uniform':
return t.style === 'param' && t.access === 'read';
case 'workgroup':
return t.access === 'read_write';
case 'function':
case 'private':
return t.style === 'param' && t.access === 'read_write';
default:
return true;
}
})
)
.fn(t => {
if (
t.params.style === 'param' &&
!(t.params.aspace === 'function' || t.params.aspace === 'private')
) {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
}

const aspaceOK = t.params.aspace === 'storage' || t.params.aspace === 'workgroup';
const accessOK = t.params.access === 'read_write';
t.expectCompileResult(
aspaceOK && accessOK,
generateAtomicCode(
t.params.type,
t.params.access,
t.params.aspace,
t.params.style,
t.params.op
)
);
});
145 changes: 145 additions & 0 deletions src/webgpu/shader/validation/types/atomics.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
export const description = `
Validation tests for atomic types
Tests covered:
* Base type
* Address spaces
* Invalid operations (non-exhaustive)
Note: valid operations (e.g. atomic built-in functions) are tested in the builtin tests.
`;

import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { keysOf } from '../../../../common/util/data_tables.js';
import { ShaderValidationTest } from '../shader_validation_test.js';

export const g = makeTestGroup(ShaderValidationTest);

g.test('type')
.desc('Test of the underlying atomic data type')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#atomic-types')
.params(u =>
u.combine('type', [
'u32',
'i32',
'f32',
'f16',
'bool',
'vec2u',
'vec3i',
'vec4f',
'mat2x2f',
'R',
'S',
'array<u32, 1>',
'array<i32, 4>',
'array<u32>',
'array<i32>',
'atomic<u32>',
'atomic<i32>',
] as const)
)
.beforeAllSubcases(t => {
if (t.params.type === 'f16') {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const code = `
struct S {
x : u32
}
struct T {
x : i32
}
struct R {
x : f32
}
struct Test {
x : atomic<${t.params.type}>
}
`;

const expect = t.params.type === 'u32' || t.params.type === 'i32';
t.expectCompileResult(expect, code);
});

g.test('address_space')
.desc('Test allowed address spaces for atomics')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#atomic-types')
.params(u =>
u
.combine('aspace', [
'storage',
'workgroup',
'storage-ro',
'uniform',
'private',
'function',
'function-let',
] as const)
.beginSubcases()
.combine('type', ['i32', 'u32'] as const)
)
.fn(t => {
let moduleVar = ``;
let functionVar = '';
switch (t.params.aspace) {
case 'storage-ro':
moduleVar = `@group(0) @binding(0) var<storage> x : atomic<${t.params.type}>;\n`;
break;
case 'storage':
moduleVar = `@group(0) @binding(0) var<storage, read_write> x : atomic<${t.params.type}>;\n`;
break;
case 'uniform':
moduleVar = `@group(0) @binding(0) var<uniform> x : atomic<${t.params.type}>;\n`;
break;
case 'workgroup':
case 'private':
moduleVar = `var<${t.params.aspace}> x : atomic<${t.params.type}>;\n`;
break;
case 'function':
functionVar = `var x : atomic<${t.params.type}>;\n`;
break;
case 'function-let':
functionVar = `let x : atomic<${t.params.type}>;\n`;
break;
}
const code = `
${moduleVar}
fn foo() {
${functionVar}
}
`;

const expect = t.params.aspace === 'storage' || t.params.aspace === 'workgroup';
t.expectCompileResult(expect, code);
});

const kInvalidOperations = {
add: `a1 + a2`,
load: `a1`,
store: `a1 = 1u`,
deref: `*a1 = 1u`,
equality: `a1 == a2`,
abs: `abs(a1)`,
address_abs: `abs(&a1)`,
};

g.test('invalid_operations')
.desc('Tests that a selection of invalid operations are invalid')
.params(u => u.combine('op', keysOf(kInvalidOperations)))
.fn(t => {
const code = `
var<workgroup> a1 : atomic<u32>;
var<workgroup> a2 : atomic<u32>;
fn foo() {
let x : u32 = ${kInvalidOperations[t.params.op]};
}
`;

t.expectCompileResult(false, code);
});

0 comments on commit e6ef0b8

Please sign in to comment.