Skip to content

Commit 5bca901

Browse files
committed
uniformity: test uniformity when a loop body only returns
Consider: loop { s1 continuing { s2 } } If the statement behaviour of s1 is exactly {Return}, then the statement behaviour of the entire loop is {Return}. In particular, nonuniformity effects do not leak out from s2 to the context after or outside of the whole loop. See the WGSL spec fix for gpuweb/gpuweb#5100 Test three new statement scenarios, each where s1 starts with `return;`, but varying where a collective operation is placed: - immediately after the return statement - in the continuing block of the same loop - immediately after the loop, where the continuing block in the loop has a break-if. This case requires the analysis to avoid using a Next behaviour from the overall loop. - immediately after the loop Also test similar variants where we use `break` instead of `return`. Also test loop constructs: - for with a condition: desugars to loop with initial `if !(cond) { break;}` - for without a condition: desugars to loop without that initial conditional break. - while loop Fixed: #4476
1 parent e34f99a commit 5bca901

File tree

1 file changed

+282
-38
lines changed

1 file changed

+282
-38
lines changed

src/webgpu/shader/validation/uniformity/uniformity.spec.ts

Lines changed: 282 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -185,60 +185,296 @@ function generateOp(op: string): string {
185185
}
186186
}
187187

188-
const kStatementKinds = ['if', 'for', 'while', 'switch', 'break-if'] as const;
188+
const kStatementKinds = [
189+
'if',
190+
'for',
191+
'while',
192+
'switch',
193+
'break-if',
194+
'loop-always-break-op-inside',
195+
'loop-always-return-op-inside',
196+
'loop-always-break-op-continuing',
197+
'loop-always-return-op-continuing',
198+
'loop-always-break-op-after',
199+
'loop-always-return-op-after',
200+
'for-with-cond-always-break-op-inside',
201+
'for-with-cond-always-return-op-inside',
202+
'for-with-cond-always-break-op-after',
203+
'for-with-cond-always-return-op-after',
204+
'for-without-cond-always-break-op-inside',
205+
'for-without-cond-always-return-op-inside',
206+
'for-without-cond-always-break-op-after',
207+
'for-without-cond-always-return-op-after',
208+
'while-always-break-op-inside',
209+
'while-always-return-op-inside',
210+
'while-always-break-op-after',
211+
'while-always-return-op-after',
212+
] as const;
189213
type kStatementType = (typeof kStatementKinds)[number];
190214

215+
type kSnippetInfo = {
216+
// A WGSL code sippet that embeds a condition and operation-operation-under-test
217+
// in a larger construct.
218+
code: string;
219+
// Is the operation-under-test sensitive to the uniformity of the condition?
220+
sensitive: boolean;
221+
};
191222
function generateConditionalStatement(
192223
statement: kStatementType,
193-
condition: string,
194-
op: string
195-
): string {
196-
const code = ``;
224+
condition_name: string,
225+
op_name: string
226+
): kSnippetInfo {
227+
const cond = generateCondition(condition_name);
228+
const uniform_cond = generateCondition('uniform_storage_ro');
229+
const op = generateOp(op_name);
197230
switch (statement) {
198231
case 'if': {
199-
return `if ${generateCondition(condition)} {
200-
${generateOp(op)};
201-
}
202-
`;
232+
return {
233+
sensitive: true,
234+
code: `
235+
if ${cond} {
236+
${op};
237+
}`,
238+
};
203239
}
204240
case 'for': {
205-
return `for (; ${generateCondition(condition)};) {
206-
${generateOp(op)};
207-
}
208-
`;
241+
return {
242+
sensitive: true,
243+
code: `
244+
for (; ${cond}; ) {
245+
${op};
246+
}`,
247+
};
209248
}
210249
case 'while': {
211-
return `while ${generateCondition(condition)} {
212-
${generateOp(op)};
213-
}
214-
`;
250+
return {
251+
sensitive: true,
252+
code: `
253+
while ${cond} {
254+
${op};
255+
}`,
256+
};
215257
}
216258
case 'switch': {
217-
return `switch u32(${generateCondition(condition)}) {
218-
case 0: {
219-
${generateOp(op)};
220-
}
221-
default: { }
222-
}
223-
`;
259+
return {
260+
sensitive: true,
261+
code: `
262+
switch u32(${cond}) {
263+
case 0: {
264+
${op};
265+
}
266+
default: { }
267+
}`,
268+
};
224269
}
225270
case 'break-if': {
226271
// The initial 'if' prevents the loop from being infinite. Its condition
227272
// is uniform, to ensure the first iteration of the the body executes
228273
// uniformly. The uniformity of the second iteration depends entirely on
229274
// the uniformity of the break-if condition.
230-
return `loop {
231-
if ${generateCondition('uniform_storage_ro')} { break; }
232-
${generateOp(op)}
233-
continuing {
234-
break if ${generateCondition(condition)};
235-
}
236-
}
237-
`;
275+
return {
276+
sensitive: true,
277+
code: `
278+
loop {
279+
if ${uniform_cond} { break; }
280+
${op}
281+
continuing {
282+
break if ${cond};
283+
}
284+
}`,
285+
};
286+
}
287+
case 'loop-always-break-op-inside': {
288+
return {
289+
sensitive: false, // The op is unreachable.
290+
code: `
291+
loop {
292+
break;
293+
if ${cond} { ${op} }
294+
}`,
295+
};
296+
}
297+
case 'loop-always-return-op-inside': {
298+
return {
299+
sensitive: false, // The op is unreachable.
300+
code: `
301+
loop {
302+
return;
303+
if ${cond} { ${op} }
304+
}`,
305+
};
306+
}
307+
case 'loop-always-break-op-continuing': {
308+
return {
309+
sensitive: false, // The op is unreachable.
310+
code: `
311+
loop {
312+
break;
313+
continuing {
314+
if ${cond} { ${op} }
315+
}
316+
}`,
317+
};
318+
}
319+
case 'loop-always-return-op-continuing': {
320+
return {
321+
sensitive: false, // The op is unreachable.
322+
code: `
323+
loop {
324+
return;
325+
continuing {
326+
if ${cond} { ${op} }
327+
}
328+
}`,
329+
};
330+
}
331+
case 'loop-always-break-op-after': {
332+
return {
333+
sensitive: true,
334+
code: `
335+
loop {
336+
break;
337+
}
338+
if ${cond} { ${op} }`,
339+
};
340+
}
341+
case 'loop-always-return-op-after': {
342+
return {
343+
sensitive: false, // The op is unreachable.
344+
code: `
345+
loop {
346+
return;
347+
}
348+
if ${cond} { ${op} }`,
349+
};
350+
}
351+
case 'for-with-cond-always-break-op-inside': {
352+
return {
353+
sensitive: false, // The op is unreachable.
354+
code: `
355+
for ( ;${uniform_cond}; ) {
356+
break;
357+
if ${cond} { ${op} }
358+
}`,
359+
};
360+
}
361+
case 'for-with-cond-always-return-op-inside': {
362+
return {
363+
sensitive: false, // The op is unreachable.
364+
code: `
365+
for ( ;${uniform_cond}; ) {
366+
return;
367+
if ${cond} { ${op} }
368+
}`,
369+
};
370+
}
371+
case 'for-with-cond-always-break-op-after': {
372+
return {
373+
sensitive: true,
374+
code: `
375+
for ( ;${uniform_cond}; ) {
376+
break;
377+
}
378+
if ${cond} { ${op} }`,
379+
};
380+
}
381+
case 'for-with-cond-always-return-op-after': {
382+
return {
383+
// Desugars to a loop with a conditional break,
384+
// before reaching the loop.
385+
sensitive: true,
386+
code: `
387+
for ( ;${uniform_cond}; ) {
388+
return;
389+
}
390+
if ${cond} { ${op} }`,
391+
};
392+
}
393+
case 'for-without-cond-always-break-op-inside': {
394+
return {
395+
sensitive: false, // The op is unreachable.
396+
code: `
397+
for ( ; ; ) {
398+
break;
399+
if ${cond} { ${op} }
400+
}`,
401+
};
402+
}
403+
case 'for-without-cond-always-return-op-inside': {
404+
return {
405+
sensitive: false, // The op is unreachable.
406+
code: `
407+
for ( ; ; ) {
408+
return;
409+
if ${cond} { ${op} }
410+
}`,
411+
};
412+
}
413+
case 'for-without-cond-always-break-op-after': {
414+
return {
415+
sensitive: true,
416+
code: `
417+
for ( ; ; ) {
418+
break;
419+
}
420+
if ${cond} { ${op} }`,
421+
};
422+
}
423+
case 'for-without-cond-always-return-op-after': {
424+
return {
425+
// Desugars to a loop without a conditional break.
426+
// So the op is unreachable.
427+
sensitive: false,
428+
code: `
429+
for ( ; ; ) {
430+
return;
431+
}
432+
if ${cond} { ${op} }`,
433+
};
434+
}
435+
case 'while-always-break-op-inside': {
436+
return {
437+
sensitive: false, // The op is unreachable.
438+
code: `
439+
while (${uniform_cond}) {
440+
break;
441+
if ${cond} { ${op} }
442+
}`,
443+
};
444+
}
445+
case 'while-always-return-op-inside': {
446+
return {
447+
sensitive: false, // The op is unreachable.
448+
code: `
449+
while (${uniform_cond}) {
450+
return;
451+
if ${cond} { ${op} }
452+
}`,
453+
};
454+
}
455+
case 'while-always-break-op-after': {
456+
return {
457+
sensitive: true,
458+
code: `
459+
while (${uniform_cond}) {
460+
break;
461+
}
462+
if ${cond} { ${op} }`,
463+
};
464+
}
465+
case 'while-always-return-op-after': {
466+
return {
467+
// Desugars to a loop with a conditional break,
468+
// before reaching the loop.
469+
sensitive: true,
470+
code: `
471+
while (${uniform_cond}) {
472+
return;
473+
}
474+
if ${cond} { ${op} }`,
475+
};
238476
}
239477
}
240-
241-
return code;
242478
}
243479

244480
g.test('basics')
@@ -293,11 +529,15 @@ g.test('basics')
293529
`;
294530

295531
// Simple control statement containing the op.
296-
code += generateConditionalStatement(t.params.statement, t.params.cond, t.params.op);
532+
const snippet = generateConditionalStatement(t.params.statement, t.params.cond, t.params.op);
533+
code += snippet.code;
297534

298535
code += `\n}\n`;
299536

300-
t.expectCompileResult(t.params.expectation || t.params.op.startsWith('control_case'), code);
537+
t.expectCompileResult(
538+
t.params.expectation || t.params.op.startsWith('control_case') || !snippet.sensitive,
539+
code
540+
);
301541
});
302542

303543
const kSubgroupOps = [
@@ -380,11 +620,15 @@ g.test('basics,subgroups')
380620
`;
381621

382622
// Simple control statement containing the op.
383-
code += generateConditionalStatement(t.params.statement, t.params.cond, t.params.op);
623+
const snippet = generateConditionalStatement(t.params.statement, t.params.cond, t.params.op);
624+
code += snippet.code;
384625

385626
code += `\n}\n`;
386627

387-
t.expectCompileResult(t.params.expectation || t.params.op.startsWith('control_case'), code);
628+
t.expectCompileResult(
629+
t.params.expectation || t.params.op.startsWith('control_case') || !snippet.sensitive,
630+
code
631+
);
388632
});
389633

390634
const kFragmentBuiltinValues = [

0 commit comments

Comments
 (0)