uniformity: cover relaxed rules for loop with returns (#4514)
Cover the relaxation of uniformity rules when the loop body has a return in it. Corresponds to https://github.com/gpuweb/gpuweb/issues/5364 At a lower level: - Rewrite the Snippet class, and use a trinary Verdict enum. - Use a simple scheme for converting a test name into a code snippet. This makes it reasonable to cover many many more loop variations. Fixes: #4512
This commit is contained in:
parent
ef27c0b88b
commit
e7cad0143f
3 changed files with 497 additions and 295 deletions
69
src/unittests/uniformity_snippet.spec.ts
Normal file
69
src/unittests/uniformity_snippet.spec.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
export const description = `
|
||||
Test for shader uniformity code snippet generation.
|
||||
`;
|
||||
|
||||
import { makeTestGroup } from '../common/framework/test_group.js';
|
||||
import { specToCode } from '../webgpu/shader/validation/uniformity/snippet.js';
|
||||
|
||||
import { UnitTest } from './unit_test.js';
|
||||
|
||||
class F extends UnitTest {
|
||||
test(spec: string, expect: string): void {
|
||||
const got = specToCode(spec);
|
||||
this.expect(
|
||||
expect === got,
|
||||
`
|
||||
expected: ${expect}
|
||||
got: ${got}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const g = makeTestGroup(F);
|
||||
|
||||
g.test('strings').fn(t => {
|
||||
t.test(
|
||||
'loop-end',
|
||||
` loop {
|
||||
}
|
||||
`
|
||||
),
|
||||
t.test(
|
||||
'loop-cond-break-op',
|
||||
` loop {
|
||||
if <cond> {break;}
|
||||
<op>
|
||||
}
|
||||
`
|
||||
),
|
||||
t.test(
|
||||
'for-unif-always-return-op',
|
||||
` for (;<uniform_cond>;) {
|
||||
return;
|
||||
<op>
|
||||
}
|
||||
`
|
||||
),
|
||||
t.test(
|
||||
'loop-op-continuing-cond-break',
|
||||
` loop {
|
||||
<op>
|
||||
continuing {
|
||||
break if <cond>;
|
||||
}
|
||||
}
|
||||
`
|
||||
),
|
||||
t.test(
|
||||
// This is the case suggested in https://github.com/gpuweb/cts/pull/4477#issuecomment-3408419425
|
||||
'loop-always-return-continuing-cond-break-end-op',
|
||||
` loop {
|
||||
return;
|
||||
continuing {
|
||||
break if <cond>;
|
||||
}
|
||||
}
|
||||
<op>
|
||||
`
|
||||
);
|
||||
});
|
||||
186
src/webgpu/shader/validation/uniformity/snippet.ts
Normal file
186
src/webgpu/shader/validation/uniformity/snippet.ts
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
export const description = 'Utilities for generating code snippets for uniformity tests';
|
||||
|
||||
import { assert, unreachable } from '../../../../common/util/util.js';
|
||||
|
||||
export type Verdict =
|
||||
// sensitive: fail uniformity analysis if and only if
|
||||
// - the condition is non-uniform, and
|
||||
// - the operation requires uniformity
|
||||
| 'sensitive'
|
||||
|
||||
// forbid: fail uniformity analysis if and only if
|
||||
// - the operation requires uniformity
|
||||
| 'forbid'
|
||||
|
||||
// permit: always passes uniformity analysis
|
||||
| 'permit';
|
||||
|
||||
export function compileShouldSucceed({
|
||||
requires_uniformity,
|
||||
condition_is_uniform,
|
||||
verdict,
|
||||
}: {
|
||||
requires_uniformity: boolean;
|
||||
condition_is_uniform: boolean;
|
||||
verdict: Verdict;
|
||||
}): boolean {
|
||||
switch (verdict) {
|
||||
case 'sensitive':
|
||||
return !requires_uniformity || condition_is_uniform;
|
||||
case 'forbid':
|
||||
return !requires_uniformity;
|
||||
case 'permit':
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export type Snippet = {
|
||||
// A unique name for the case.
|
||||
name: string;
|
||||
// A WGSL code sippet that optionally embeds items to replaced later:
|
||||
// - '<op>', an operation that does or does not require uniformity.
|
||||
// - '<cond>', a condition which will be uniform or non-uniform
|
||||
code: string;
|
||||
// What is the verdict for this code snippet, after substitution of
|
||||
// the operation and condition.
|
||||
verdict: Verdict;
|
||||
};
|
||||
|
||||
// We use a small domain-specific language that converts a
|
||||
// string into a code snippet.
|
||||
//
|
||||
// NOTE: If you're confused about this scheme, see the unit tests
|
||||
// in src/unittests/uniformity_snippet.spec.ts. Run them
|
||||
// with `npm run unittest`.
|
||||
//
|
||||
// We process the name from left to right
|
||||
// using the following component naming scheme:
|
||||
// <kind-of-loop>, always appears first
|
||||
// 'loop'
|
||||
// 'for', for-loop with without a condition
|
||||
// 'for-unif', for-loop with a uniform loop condition
|
||||
// 'for-nonunif', for-loop with a non-uniform loop condition
|
||||
// 'while-unif', while-loop with uniform loop condition
|
||||
// 'while-nonunif', while-loop with non-uniform loop condition
|
||||
// The next components are listed in order they appear in the code.
|
||||
// <interrupt> :
|
||||
// always-break, cond-break,
|
||||
// always-return, cond-return,
|
||||
// always-continue, cond-continue,
|
||||
// The 'cond' variations will use a condition, either uniform
|
||||
// or non-uniform, to be substituted later.
|
||||
// 'unif-break': a loop break with uniform loop condition, used
|
||||
// to avoid rejection due to infinite loop checks.
|
||||
// 'op':
|
||||
// 'continuing': indicates start of continuing block
|
||||
// 'end': indicates end of the loop
|
||||
|
||||
type LoopKind = 'loop' | 'for' | 'for-unif' | 'for-nonunif' | 'while-unif' | 'while-nonunif';
|
||||
|
||||
// Expand a loop case spec to its shader code
|
||||
export function specToCode(spec: string): string {
|
||||
let matches = spec.match('^(loop|for-unif|for-nonunif|for|while-unif|while-nonunif)-(.*)');
|
||||
assert(matches !== null, `invalid spec string: ${spec}`);
|
||||
|
||||
let prefix = ' ';
|
||||
const parts = [];
|
||||
const end_parts = [prefix, '}\n']; // closing brace
|
||||
|
||||
const kind = matches[1] as LoopKind;
|
||||
let rest = matches[2];
|
||||
parts.push(prefix);
|
||||
switch (kind) {
|
||||
case 'loop':
|
||||
parts.push('loop {');
|
||||
break;
|
||||
case 'for':
|
||||
parts.push('for (;;) {');
|
||||
break;
|
||||
case 'for-unif':
|
||||
parts.push(`for (;<uniform_cond>;) {`);
|
||||
break;
|
||||
case 'for-nonunif':
|
||||
parts.push(`for (;<nonuniform_cond>;) {`);
|
||||
break;
|
||||
case 'while-unif':
|
||||
parts.push(`while (<uniform_cond>) {`);
|
||||
break;
|
||||
case 'while-nonunif':
|
||||
parts.push(`while (<nonuniform_cond>) {`);
|
||||
break;
|
||||
}
|
||||
parts.push('\n');
|
||||
|
||||
let in_continuing = false;
|
||||
prefix = ' ';
|
||||
while (rest.length > 0) {
|
||||
const current_len = rest.length;
|
||||
matches = rest.match(
|
||||
'^(op|continuing|end|unif-break|always-break|cond-break|unif-break|always-return|cond-return|always-continue|cond-continue)(-|$)(.*)'
|
||||
);
|
||||
assert(matches !== null, `invalid spec string: ${spec}`);
|
||||
const elem = matches[1];
|
||||
rest = matches[3];
|
||||
assert(rest.length < current_len, `pattern is not shrinking: '${rest}', from ${spec}`);
|
||||
switch (elem) {
|
||||
case 'op':
|
||||
parts.push(prefix, '<op>\n'); // to be replaced later.
|
||||
break;
|
||||
case 'end': // end the loop
|
||||
if (in_continuing) {
|
||||
prefix = ' ';
|
||||
}
|
||||
prefix = ' ';
|
||||
parts.push(...end_parts);
|
||||
end_parts.length = 0;
|
||||
in_continuing = false;
|
||||
break;
|
||||
case 'continuing':
|
||||
parts.push(prefix, 'continuing {\n');
|
||||
end_parts.unshift(prefix, '}\n');
|
||||
in_continuing = true;
|
||||
prefix = ' ';
|
||||
break;
|
||||
case 'unif-break':
|
||||
assert(!in_continuing);
|
||||
parts.push(prefix, `if <uniform_cond> {break;}\n`);
|
||||
break;
|
||||
case 'always-break':
|
||||
assert(!in_continuing);
|
||||
parts.push(prefix, 'break;\n');
|
||||
break;
|
||||
case 'cond-break':
|
||||
if (in_continuing) {
|
||||
parts.push(prefix, `break if <cond>;\n`);
|
||||
} else {
|
||||
parts.push(prefix, `if <cond> {break;}\n`);
|
||||
}
|
||||
break;
|
||||
case 'always-return':
|
||||
assert(!in_continuing);
|
||||
parts.push(prefix, 'return;\n');
|
||||
break;
|
||||
case 'cond-return':
|
||||
assert(!in_continuing);
|
||||
parts.push(prefix, `if <cond> {return;}\n`);
|
||||
break;
|
||||
case 'always-continue':
|
||||
assert(!in_continuing);
|
||||
parts.push(prefix, 'continue;\n');
|
||||
break;
|
||||
case 'cond-continue':
|
||||
assert(!in_continuing);
|
||||
parts.push(prefix, `if <cond> {continue;}\n`);
|
||||
break;
|
||||
default:
|
||||
unreachable(`invalid loop case spec ${spec}`);
|
||||
}
|
||||
}
|
||||
parts.push(...end_parts);
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
// Creates a Snippet from a loop spec string and a verdict.
|
||||
export function LoopCase(spec: string, verdict: Verdict): Snippet {
|
||||
return { name: spec, verdict, code: specToCode(spec) };
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ import { keysOf } from '../../../../common/util/data_tables.js';
|
|||
import { unreachable } from '../../../../common/util/util.js';
|
||||
import { ShaderValidationTest } from '../shader_validation_test.js';
|
||||
|
||||
import { Snippet, LoopCase, compileShouldSucceed } from './snippet.js';
|
||||
|
||||
export const g = makeTestGroup(ShaderValidationTest);
|
||||
|
||||
const kCollectiveOps = [
|
||||
|
|
@ -185,303 +187,240 @@ function generateOp(op: string): string {
|
|||
}
|
||||
}
|
||||
|
||||
const kStatementKinds = [
|
||||
'if',
|
||||
'for',
|
||||
'while',
|
||||
'switch',
|
||||
'break-if',
|
||||
'loop-always-break-op-inside',
|
||||
'loop-always-return-op-inside',
|
||||
'loop-always-break-op-continuing',
|
||||
'loop-always-return-op-continuing',
|
||||
'loop-always-break-op-after',
|
||||
'loop-always-return-op-after',
|
||||
'for-with-cond-always-break-op-inside',
|
||||
'for-with-cond-always-return-op-inside',
|
||||
'for-with-cond-always-break-op-after',
|
||||
'for-with-cond-always-return-op-after',
|
||||
'for-without-cond-always-break-op-inside',
|
||||
'for-without-cond-always-return-op-inside',
|
||||
'for-without-cond-always-break-op-after',
|
||||
'for-without-cond-always-return-op-after',
|
||||
'while-always-break-op-inside',
|
||||
'while-always-return-op-inside',
|
||||
'while-always-break-op-after',
|
||||
'while-always-return-op-after',
|
||||
] as const;
|
||||
type kStatementType = (typeof kStatementKinds)[number];
|
||||
|
||||
type kSnippetInfo = {
|
||||
// A WGSL code sippet that embeds a condition and operation-operation-under-test
|
||||
// in a larger construct.
|
||||
code: string;
|
||||
// Is the operation-under-test sensitive to the uniformity of the condition?
|
||||
sensitive: boolean;
|
||||
};
|
||||
function generateConditionalStatement(
|
||||
statement: kStatementType,
|
||||
condition_name: string,
|
||||
op_name: string
|
||||
): kSnippetInfo {
|
||||
const cond = generateCondition(condition_name);
|
||||
const uniform_cond = generateCondition('uniform_storage_ro');
|
||||
const op = generateOp(op_name);
|
||||
switch (statement) {
|
||||
case 'if': {
|
||||
return {
|
||||
sensitive: true,
|
||||
code: `
|
||||
if ${cond} {
|
||||
${op};
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'for': {
|
||||
return {
|
||||
sensitive: true,
|
||||
code: `
|
||||
for (; ${cond}; ) {
|
||||
${op};
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'while': {
|
||||
return {
|
||||
sensitive: true,
|
||||
code: `
|
||||
while ${cond} {
|
||||
${op};
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'switch': {
|
||||
return {
|
||||
sensitive: true,
|
||||
code: `
|
||||
switch u32(${cond}) {
|
||||
const kStatementCases = [
|
||||
// Basic non-loop cases.
|
||||
{
|
||||
name: 'if',
|
||||
code: 'if <cond> { <op> }',
|
||||
verdict: 'sensitive',
|
||||
},
|
||||
{
|
||||
name: 'switch',
|
||||
code: `
|
||||
switch u32(<cond>) {
|
||||
case 0: {
|
||||
${op};
|
||||
<op>
|
||||
}
|
||||
default: { }
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'break-if': {
|
||||
// The initial 'if' prevents the loop from being infinite. Its condition
|
||||
// is uniform, to ensure the first iteration of the the body executes
|
||||
// uniformly. The uniformity of the second iteration depends entirely on
|
||||
// the uniformity of the break-if condition.
|
||||
return {
|
||||
sensitive: true,
|
||||
code: `
|
||||
loop {
|
||||
if ${uniform_cond} { break; }
|
||||
${op}
|
||||
continuing {
|
||||
break if ${cond};
|
||||
}
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'loop-always-break-op-inside': {
|
||||
return {
|
||||
sensitive: false, // The op is unreachable.
|
||||
code: `
|
||||
loop {
|
||||
break;
|
||||
if ${cond} { ${op} }
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'loop-always-return-op-inside': {
|
||||
return {
|
||||
sensitive: false, // The op is unreachable.
|
||||
code: `
|
||||
loop {
|
||||
return;
|
||||
if ${cond} { ${op} }
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'loop-always-break-op-continuing': {
|
||||
return {
|
||||
sensitive: false, // The op is unreachable.
|
||||
code: `
|
||||
loop {
|
||||
break;
|
||||
continuing {
|
||||
if ${cond} { ${op} }
|
||||
}
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'loop-always-return-op-continuing': {
|
||||
return {
|
||||
sensitive: false, // The op is unreachable.
|
||||
code: `
|
||||
loop {
|
||||
return;
|
||||
continuing {
|
||||
if ${cond} { ${op} }
|
||||
}
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'loop-always-break-op-after': {
|
||||
return {
|
||||
sensitive: true,
|
||||
code: `
|
||||
loop {
|
||||
break;
|
||||
}
|
||||
if ${cond} { ${op} }`,
|
||||
};
|
||||
}
|
||||
case 'loop-always-return-op-after': {
|
||||
return {
|
||||
sensitive: false, // The op is unreachable.
|
||||
code: `
|
||||
loop {
|
||||
return;
|
||||
}
|
||||
if ${cond} { ${op} }`,
|
||||
};
|
||||
}
|
||||
case 'for-with-cond-always-break-op-inside': {
|
||||
return {
|
||||
sensitive: false, // The op is unreachable.
|
||||
code: `
|
||||
for ( ;${uniform_cond}; ) {
|
||||
break;
|
||||
if ${cond} { ${op} }
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'for-with-cond-always-return-op-inside': {
|
||||
return {
|
||||
sensitive: false, // The op is unreachable.
|
||||
code: `
|
||||
for ( ;${uniform_cond}; ) {
|
||||
return;
|
||||
if ${cond} { ${op} }
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'for-with-cond-always-break-op-after': {
|
||||
return {
|
||||
sensitive: true,
|
||||
code: `
|
||||
for ( ;${uniform_cond}; ) {
|
||||
break;
|
||||
}
|
||||
if ${cond} { ${op} }`,
|
||||
};
|
||||
}
|
||||
case 'for-with-cond-always-return-op-after': {
|
||||
return {
|
||||
// Desugars to a loop with a conditional break,
|
||||
// before reaching the loop.
|
||||
sensitive: true,
|
||||
code: `
|
||||
for ( ;${uniform_cond}; ) {
|
||||
return;
|
||||
}
|
||||
if ${cond} { ${op} }`,
|
||||
};
|
||||
}
|
||||
case 'for-without-cond-always-break-op-inside': {
|
||||
return {
|
||||
sensitive: false, // The op is unreachable.
|
||||
code: `
|
||||
for ( ; ; ) {
|
||||
break;
|
||||
if ${cond} { ${op} }
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'for-without-cond-always-return-op-inside': {
|
||||
return {
|
||||
sensitive: false, // The op is unreachable.
|
||||
code: `
|
||||
for ( ; ; ) {
|
||||
return;
|
||||
if ${cond} { ${op} }
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'for-without-cond-always-break-op-after': {
|
||||
return {
|
||||
sensitive: true,
|
||||
code: `
|
||||
for ( ; ; ) {
|
||||
break;
|
||||
}
|
||||
if ${cond} { ${op} }`,
|
||||
};
|
||||
}
|
||||
case 'for-without-cond-always-return-op-after': {
|
||||
return {
|
||||
// Desugars to a loop without a conditional break.
|
||||
// So the op is unreachable.
|
||||
sensitive: false,
|
||||
code: `
|
||||
for ( ; ; ) {
|
||||
return;
|
||||
}
|
||||
if ${cond} { ${op} }`,
|
||||
};
|
||||
}
|
||||
case 'while-always-break-op-inside': {
|
||||
return {
|
||||
sensitive: false, // The op is unreachable.
|
||||
code: `
|
||||
while (${uniform_cond}) {
|
||||
break;
|
||||
if ${cond} { ${op} }
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'while-always-return-op-inside': {
|
||||
return {
|
||||
sensitive: false, // The op is unreachable.
|
||||
code: `
|
||||
while (${uniform_cond}) {
|
||||
return;
|
||||
if ${cond} { ${op} }
|
||||
}`,
|
||||
};
|
||||
}
|
||||
case 'while-always-break-op-after': {
|
||||
return {
|
||||
sensitive: true,
|
||||
code: `
|
||||
while (${uniform_cond}) {
|
||||
break;
|
||||
}
|
||||
if ${cond} { ${op} }`,
|
||||
};
|
||||
}
|
||||
case 'while-always-return-op-after': {
|
||||
return {
|
||||
// Desugars to a loop with a conditional break,
|
||||
// before reaching the loop.
|
||||
sensitive: true,
|
||||
code: `
|
||||
while (${uniform_cond}) {
|
||||
return;
|
||||
}
|
||||
if ${cond} { ${op} }`,
|
||||
};
|
||||
}
|
||||
}
|
||||
verdict: 'sensitive',
|
||||
},
|
||||
|
||||
// Loops
|
||||
|
||||
// loop without continuing
|
||||
// op before the interruption
|
||||
LoopCase('loop-op-always-break', 'permit'),
|
||||
LoopCase('loop-op-cond-break', 'sensitive'),
|
||||
LoopCase('loop-op-always-return', 'permit'),
|
||||
LoopCase('loop-op-cond-return', 'sensitive'),
|
||||
LoopCase('loop-unif-break-op-always-continue', 'permit'),
|
||||
LoopCase('loop-unif-break-op-cond-continue', 'sensitive'),
|
||||
|
||||
// op after the interruption
|
||||
LoopCase('loop-always-break-op', 'permit'),
|
||||
LoopCase('loop-cond-break-op', 'sensitive'),
|
||||
LoopCase('loop-always-return-op', 'permit'),
|
||||
LoopCase('loop-cond-return-op', 'sensitive'),
|
||||
LoopCase('loop-unif-break-always-continue-op', 'permit'),
|
||||
LoopCase('loop-unif-break-cond-continue-op', 'sensitive'),
|
||||
|
||||
// op after the end of the loop
|
||||
// Without a return, any non-uniformity introduced in the
|
||||
// loop is resolved by the end of the loop.
|
||||
LoopCase('loop-always-break-end-op', 'permit'),
|
||||
LoopCase('loop-unif-break-end-op', 'permit'),
|
||||
LoopCase('loop-cond-break-end-op', 'permit'),
|
||||
LoopCase('loop-always-return-end-op', 'permit'),
|
||||
LoopCase('loop-cond-return-end-op', 'permit'), // the loop can only return
|
||||
LoopCase('loop-unif-break-always-continue-end-op', 'permit'),
|
||||
LoopCase('loop-unif-break-cond-continue-end-op', 'permit'),
|
||||
|
||||
// loop with continuing block
|
||||
// op before the interruption before continuing
|
||||
LoopCase('loop-op-always-break-continuing', 'permit'),
|
||||
LoopCase('loop-op-unif-break-continuing', 'permit'),
|
||||
LoopCase('loop-op-cond-break-continuing', 'sensitive'),
|
||||
LoopCase('loop-op-always-return-continuing', 'permit'),
|
||||
LoopCase('loop-op-cond-return-continuing', 'sensitive'),
|
||||
LoopCase('loop-unif-break-op-always-continue-continuing', 'permit'),
|
||||
// non re-convergence at the continuing block.
|
||||
LoopCase('loop-unif-break-op-cond-continue-continuing', 'sensitive'),
|
||||
|
||||
// op in body, interruption in continiuing
|
||||
// The only permitted interruption in the continuing block
|
||||
// is cond-break.
|
||||
LoopCase('loop-op-continuing-cond-break', 'sensitive'),
|
||||
|
||||
// interruption in body, op in continuing
|
||||
LoopCase('loop-always-break-continuing-op', 'permit'),
|
||||
LoopCase('loop-cond-break-continuing-op', 'sensitive'),
|
||||
LoopCase('loop-always-return-continuing-op', 'permit'),
|
||||
LoopCase('loop-cond-return-continuing-op', 'sensitive'),
|
||||
|
||||
// op and interruption in continuing
|
||||
LoopCase('loop-continuing-op-cond-break', 'sensitive'),
|
||||
|
||||
// interruption in body, op after end
|
||||
LoopCase('loop-always-break-continuing-end-op', 'permit'),
|
||||
LoopCase('loop-cond-break-continuing-end-op', 'permit'),
|
||||
LoopCase('loop-always-return-continuing-end-op', 'permit'),
|
||||
LoopCase('loop-cond-return-continuing-end-op', 'permit'), // the looop can only return
|
||||
LoopCase('loop-unif-break-always-continue-continuing-end-op', 'permit'),
|
||||
LoopCase('loop-unif-break-cond-continue-continuing-end-op', 'permit'),
|
||||
|
||||
// interruption in continuing, op after end
|
||||
LoopCase('loop-continuing-cond-break-end-op', 'permit'),
|
||||
LoopCase('loop-always-break-continuing-cond-break-end-op', 'permit'),
|
||||
LoopCase('loop-always-return-continuing-cond-break-end-op', 'permit'),
|
||||
|
||||
// Unconditional for
|
||||
// interruption then op
|
||||
LoopCase('for-always-break-op', 'permit'),
|
||||
LoopCase('for-cond-break-op', 'sensitive'),
|
||||
LoopCase('for-always-return-op', 'permit'),
|
||||
LoopCase('for-cond-return-op', 'sensitive'),
|
||||
LoopCase('for-unif-unif-break-always-continue-op', 'permit'),
|
||||
LoopCase('for-unif-unif-break-cond-continue-op', 'sensitive'),
|
||||
// op then interruption
|
||||
LoopCase('for-op-always-break', 'permit'),
|
||||
LoopCase('for-op-cond-break', 'sensitive'),
|
||||
LoopCase('for-op-always-return', 'permit'),
|
||||
LoopCase('for-op-cond-return', 'sensitive'),
|
||||
LoopCase('for-op-unif-break-always-continue', 'permit'),
|
||||
LoopCase('for-op-unif-break-cond-continue', 'sensitive'),
|
||||
|
||||
// For with uniform condition
|
||||
LoopCase('for-unif-op', 'permit'),
|
||||
// interruption, then op
|
||||
LoopCase('for-unif-always-break-op', 'permit'),
|
||||
LoopCase('for-unif-cond-break-op', 'sensitive'),
|
||||
LoopCase('for-unif-always-return-op', 'permit'),
|
||||
LoopCase('for-unif-cond-return-op', 'sensitive'),
|
||||
LoopCase('for-unif-always-continue-op', 'permit'),
|
||||
LoopCase('for-unif-cond-continue-op', 'sensitive'),
|
||||
// op, then interruption
|
||||
LoopCase('for-unif-op-always-break', 'permit'),
|
||||
LoopCase('for-unif-op-cond-break', 'sensitive'),
|
||||
LoopCase('for-unif-op-always-return', 'permit'),
|
||||
LoopCase('for-unif-op-cond-return', 'sensitive'),
|
||||
LoopCase('for-unif-op-always-continue', 'permit'),
|
||||
LoopCase('for-unif-op-cond-continue', 'sensitive'),
|
||||
// interruption, then op after loop
|
||||
LoopCase('for-unif-end-op', 'permit'),
|
||||
LoopCase('for-unif-always-break-end-op', 'permit'),
|
||||
LoopCase('for-unif-cond-break-end-op', 'permit'),
|
||||
LoopCase('for-unif-always-return-end-op', 'permit'),
|
||||
LoopCase('for-unif-cond-return-end-op', 'sensitive'),
|
||||
LoopCase('for-unif-always-continue-end-op', 'permit'),
|
||||
LoopCase('for-unif-cond-continue-end-op', 'permit'),
|
||||
|
||||
// For with non-uniform condition
|
||||
LoopCase('for-nonunif-op', 'forbid'),
|
||||
// interruption, then op
|
||||
LoopCase('for-nonunif-always-break-op', 'permit'),
|
||||
LoopCase('for-nonunif-cond-break-op', 'forbid'),
|
||||
LoopCase('for-nonunif-always-return-op', 'permit'),
|
||||
LoopCase('for-nonunif-cond-return-op', 'forbid'),
|
||||
LoopCase('for-nonunif-always-continue-op', 'permit'),
|
||||
LoopCase('for-nonunif-cond-continue-op', 'forbid'),
|
||||
// op, then interruption
|
||||
LoopCase('for-nonunif-op-always-break', 'forbid'),
|
||||
LoopCase('for-nonunif-op-cond-break', 'forbid'),
|
||||
LoopCase('for-nonunif-op-always-return', 'forbid'),
|
||||
LoopCase('for-nonunif-op-cond-return', 'forbid'),
|
||||
LoopCase('for-nonunif-op-always-continue', 'forbid'),
|
||||
LoopCase('for-nonunif-op-cond-continue', 'forbid'),
|
||||
// interruption, then op after loop
|
||||
LoopCase('for-nonunif-end-op', 'permit'),
|
||||
LoopCase('for-nonunif-always-break-end-op', 'permit'),
|
||||
LoopCase('for-nonunif-cond-break-end-op', 'permit'),
|
||||
LoopCase('for-nonunif-always-return-end-op', 'forbid'),
|
||||
LoopCase('for-nonunif-cond-return-end-op', 'forbid'),
|
||||
LoopCase('for-nonunif-always-continue-end-op', 'permit'),
|
||||
LoopCase('for-nonunif-cond-continue-end-op', 'permit'),
|
||||
|
||||
// While with uniform condition
|
||||
LoopCase('while-unif-op', 'permit'),
|
||||
// interruption, then op
|
||||
LoopCase('while-unif-always-break-op', 'permit'),
|
||||
LoopCase('while-unif-cond-break-op', 'sensitive'),
|
||||
LoopCase('while-unif-always-return-op', 'permit'),
|
||||
LoopCase('while-unif-cond-return-op', 'sensitive'),
|
||||
LoopCase('while-unif-always-continue-op', 'permit'),
|
||||
LoopCase('while-unif-cond-continue-op', 'sensitive'),
|
||||
// op, then interruption
|
||||
LoopCase('while-unif-op-always-break', 'permit'),
|
||||
LoopCase('while-unif-op-cond-break', 'sensitive'),
|
||||
LoopCase('while-unif-op-always-return', 'permit'),
|
||||
LoopCase('while-unif-op-cond-return', 'sensitive'),
|
||||
LoopCase('while-unif-op-always-continue', 'permit'),
|
||||
LoopCase('while-unif-op-cond-continue', 'sensitive'),
|
||||
// interruption, then op after loop
|
||||
LoopCase('while-unif-end-op', 'permit'),
|
||||
LoopCase('while-unif-always-break-end-op', 'permit'),
|
||||
LoopCase('while-unif-cond-break-end-op', 'permit'),
|
||||
LoopCase('while-unif-always-return-end-op', 'permit'),
|
||||
LoopCase('while-unif-cond-return-end-op', 'sensitive'),
|
||||
LoopCase('while-unif-always-continue-end-op', 'permit'),
|
||||
LoopCase('while-unif-cond-continue-end-op', 'permit'),
|
||||
|
||||
// While with non-uniform condition
|
||||
LoopCase('while-nonunif-op', 'forbid'),
|
||||
// interruption, then op
|
||||
LoopCase('while-nonunif-always-break-op', 'permit'),
|
||||
LoopCase('while-nonunif-cond-break-op', 'forbid'),
|
||||
LoopCase('while-nonunif-always-return-op', 'permit'),
|
||||
LoopCase('while-nonunif-cond-return-op', 'forbid'),
|
||||
LoopCase('while-nonunif-always-continue-op', 'permit'),
|
||||
LoopCase('while-nonunif-cond-continue-op', 'forbid'),
|
||||
// op, then interruption
|
||||
LoopCase('while-nonunif-op-always-break', 'forbid'),
|
||||
LoopCase('while-nonunif-op-cond-break', 'forbid'),
|
||||
LoopCase('while-nonunif-op-always-return', 'forbid'),
|
||||
LoopCase('while-nonunif-op-cond-return', 'forbid'),
|
||||
LoopCase('while-nonunif-op-always-continue', 'forbid'),
|
||||
LoopCase('while-nonunif-op-cond-continue', 'forbid'),
|
||||
// interruption, then op after loop
|
||||
LoopCase('while-nonunif-end-op', 'permit'),
|
||||
LoopCase('while-nonunif-always-break-end-op', 'permit'),
|
||||
LoopCase('while-nonunif-cond-break-end-op', 'permit'),
|
||||
LoopCase('while-nonunif-always-return-end-op', 'forbid'),
|
||||
LoopCase('while-nonunif-cond-return-end-op', 'forbid'),
|
||||
LoopCase('while-nonunif-always-continue-end-op', 'permit'),
|
||||
LoopCase('while-nonunif-cond-continue-end-op', 'permit'),
|
||||
];
|
||||
|
||||
const kStatementNames = kStatementCases.map(sc => sc.name);
|
||||
type StatementName = (typeof kStatementNames)[number];
|
||||
// Lookup table by statement name
|
||||
const kStatementDict = Object.fromEntries(kStatementCases.map(sc => [sc.name, sc])) as Record<
|
||||
StatementName,
|
||||
Snippet
|
||||
>;
|
||||
|
||||
function generateConditionalStatement(
|
||||
name: StatementName,
|
||||
condition_name: string,
|
||||
op_name: string
|
||||
): Snippet {
|
||||
const cond = generateCondition(condition_name);
|
||||
const op = generateOp(op_name);
|
||||
const snippet = kStatementDict[name];
|
||||
let code = snippet.code;
|
||||
code = code
|
||||
.replace('<op>', op)
|
||||
.replace('<cond>', cond)
|
||||
.replaceAll('<uniform_cond>', generateCondition('uniform_storage_ro'))
|
||||
.replaceAll('<nonuniform_cond>', generateCondition('nonuniform_storage_ro'));
|
||||
return { name, code, verdict: snippet.verdict };
|
||||
}
|
||||
|
||||
g.test('basics')
|
||||
.desc(`Test collective operations in simple uniform or non-uniform control flow.`)
|
||||
.params(u =>
|
||||
u
|
||||
.combine('statement', kStatementKinds)
|
||||
.combine('statement', kStatementNames)
|
||||
.beginSubcases()
|
||||
.combineWithParams(kConditions)
|
||||
.combineWithParams(kCollectiveOps)
|
||||
|
|
@ -522,11 +461,11 @@ g.test('basics')
|
|||
code += `@builtin(position) p : vec4<f32>`;
|
||||
}
|
||||
code += `) {
|
||||
let u_let = uniform_buffer.x;
|
||||
let n_let = rw_buffer[0];
|
||||
var u_f = uniform_buffer.z;
|
||||
var n_f = rw_buffer[1];
|
||||
`;
|
||||
let u_let = uniform_buffer.x;
|
||||
let n_let = rw_buffer[0];
|
||||
var u_f = uniform_buffer.z;
|
||||
var n_f = rw_buffer[1];
|
||||
`;
|
||||
|
||||
// Simple control statement containing the op.
|
||||
const snippet = generateConditionalStatement(t.params.statement, t.params.cond, t.params.op);
|
||||
|
|
@ -535,7 +474,11 @@ g.test('basics')
|
|||
code += `\n}\n`;
|
||||
|
||||
t.expectCompileResult(
|
||||
t.params.expectation || t.params.op.startsWith('control_case') || !snippet.sensitive,
|
||||
compileShouldSucceed({
|
||||
requires_uniformity: !t.params.op.startsWith('control_case'),
|
||||
condition_is_uniform: t.params.expectation,
|
||||
verdict: snippet.verdict,
|
||||
}),
|
||||
code
|
||||
);
|
||||
});
|
||||
|
|
@ -573,7 +516,7 @@ g.test('basics,subgroups')
|
|||
.desc(`Test subgroup operations in simple uniform or non-uniform control flow.`)
|
||||
.params(u =>
|
||||
u
|
||||
.combine('statement', kStatementKinds)
|
||||
.combine('statement', kStatementNames)
|
||||
.beginSubcases()
|
||||
.combineWithParams(kConditions)
|
||||
.combine('op', kSubgroupOps)
|
||||
|
|
@ -626,7 +569,11 @@ g.test('basics,subgroups')
|
|||
code += `\n}\n`;
|
||||
|
||||
t.expectCompileResult(
|
||||
t.params.expectation || t.params.op.startsWith('control_case') || !snippet.sensitive,
|
||||
compileShouldSucceed({
|
||||
requires_uniformity: !t.params.op.startsWith('control_case'),
|
||||
condition_is_uniform: t.params.expectation,
|
||||
verdict: snippet.verdict,
|
||||
}),
|
||||
code
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue