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:
David Neto 2025-12-01 14:10:11 -05:00 committed by GitHub
parent ef27c0b88b
commit e7cad0143f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 497 additions and 295 deletions

View 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>
`
);
});

View 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) };
}

View file

@ -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
);
});