blob: 8bfa59c5cbc07a89c03e51a6b846e169fc39b1b6 [file] [log] [blame]
export const description = `Validation tests for diagnostic directive and attribute`;
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);
const kSpecDiagnosticRules = ['derivative_uniformity', 'subgroup_uniformity'];
const kSpecDiagnosticSeverities = ['off', 'info', 'warning', 'error'];
const kDiagnosticTypes = ['attribute', 'directive'];
const kBadSeverities = ['none', 'warn', 'goose', 'fatal', 'severe'];
const kBadSingleTokenRules = ['unknown', 'blahblahblah', 'derivative_uniform'];
function generateDiagnostic(type: string, severity: string, rule: string): string {
const diagnostic = `diagnostic(${severity}, ${rule})`;
if (type === 'directive') {
return diagnostic;
} else {
return '@' + diagnostic;
}
}
const kValidLocations = {
module: (diag: string) => `${diag};`,
function: (diag: string) => `${diag} fn foo() { }`,
compound: (diag: string) => `fn foo() { ${diag} { } }`,
if_stmt: (diag: string) => `fn foo() { ${diag} if true { } }`,
if_then: (diag: string) => `fn foo() { if true ${diag} { } }`,
if_else: (diag: string) => `fn foo() { if true { } else ${diag} { } }`,
switch_stmt: (diag: string) => `fn foo() { ${diag} switch 0 { default { } } }`,
switch_body: (diag: string) => `fn foo() { switch 0 ${diag} { default { } } }`,
switch_default: (diag: string) => `fn foo() { switch 0 { default ${diag} { } } }`,
switch_case: (diag: string) => `fn foo() { switch 0 { case 0 ${diag} { } default { } } }`,
loop_stmt: (diag: string) => `fn foo() { ${diag} loop { break; } }`,
loop_body: (diag: string) => `fn foo() { loop ${diag} { break; } }`,
loop_continuing: (diag: string) => `fn foo() { loop { continuing ${diag} { break if true; } } }`,
while_stmt: (diag: string) => `fn foo() { ${diag} while true { break; } }`,
while_body: (diag: string) => `fn foo() { while true ${diag} { break; } }`,
for_stmt: (diag: string) => `fn foo() { ${diag} for (var i = 0; i < 10; i++) { } }`,
for_body: (diag: string) => `fn foo() { for (var i = 0; i < 10; i++) ${diag} { } }`,
};
const kInvalidLocations = {
module_var: (diag: string) => `${diag} var<private> x : u32;`,
module_const: (diag: string) => `${diag} const x = 0;`,
module_override: (diag: string) => `${diag} override x : u32;`,
struct: (diag: string) => `${diag} struct S { x : u32 }`,
struct_member: (diag: string) => ` struct S { ${diag} x : u32 }`,
function_params: (diag: string) => `fn foo${diag}() { }`,
function_var: (diag: string) => `fn foo() { ${diag} var x = 0; }`,
function_let: (diag: string) => `fn foo() { ${diag} let x = 0; }`,
function_const: (diag: string) => `fn foo() { ${diag} const x = 0; }`,
pre_else: (diag: string) => `fn foo() { if true { } ${diag} else { } }`,
pre_default: (diag: string) => `fn foo() { switch 0 { ${diag} default { } } }`,
pre_case: (diag: string) => `fn foo() { switch 0 { ${diag} case 0 { } default { } } }`,
pre_continuing: (diag: string) => `fn foo() { loop { ${diag} continuing { break if true; } } }`,
pre_for_params: (diag: string) => `fn foo() { for ${diag} (var i = 0; i < 10; i++) { } }`,
};
const kNestedLocations = {
compound: (d1: string, d2: string) => `${d1} fn foo() { ${d2} { } }`,
if_stmt: (d1: string, d2: string) => `fn foo() { ${d1} if true ${d2} { } }`,
switch_stmt: (d1: string, d2: string) => `fn foo() { ${d1} switch 0 ${d2} { default { } } }`,
switch_body: (d1: string, d2: string) => `fn foo() { switch 0 ${d1} { default ${d2} { } } }`,
switch_case: (d1: string, d2: string) =>
`fn foo() { switch 0 { case 0 ${d1} { } default ${d2} { } } }`,
loop_stmt: (d1: string, d2: string) => `fn foo() { ${d1} loop ${d2} { break; } }`,
while_stmt: (d1: string, d2: string) => `fn foo() { ${d1} while true ${d2} { break; } }`,
for_stmt: (d1: string, d2: string) => `fn foo() { ${d1} for (var i = 0; i < 10; i++) ${d2} { } }`,
};
g.test('valid_params')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
.desc(`Tests required accepted diagnostic parameters`)
.params(u =>
u
.combine('severity', kSpecDiagnosticSeverities)
.combine('rule', kSpecDiagnosticRules)
.combine('type', kDiagnosticTypes)
)
.fn(t => {
const diag = generateDiagnostic(t.params.type, t.params.severity, t.params.rule);
let code = ``;
if (t.params.type === 'directive') {
code = kValidLocations['module'](diag);
} else {
code = kValidLocations['function'](diag);
}
t.expectCompileResult(true, code);
});
g.test('invalid_severity')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
.desc(`Tests invalid severities are rejected`)
.params(u => u.combine('severity', kBadSeverities).combine('type', kDiagnosticTypes))
.fn(t => {
const diag = generateDiagnostic(t.params.type, t.params.severity, 'derivative_uniformity');
let code = ``;
if (t.params.type === 'directive') {
code = kValidLocations['module'](diag);
} else {
code = kValidLocations['function'](diag);
}
t.expectCompileResult(false, code);
});
g.test('warning_unknown_rule')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
.desc(`Tests unknown single token rules issue a warning`)
.params(u => u.combine('type', kDiagnosticTypes).combine('rule', kBadSingleTokenRules))
.fn(t => {
const diag = generateDiagnostic(t.params.type, 'info', t.params.rule);
let code = ``;
if (t.params.type === 'directive') {
code = kValidLocations['module'](diag);
} else {
code = kValidLocations['function'](diag);
}
t.expectCompileWarning(true, code);
});
g.test('valid_locations')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
.desc(`Tests valid locations`)
.params(u =>
u
.combine('type', kDiagnosticTypes)
.combine('location', keysOf(kValidLocations))
.combine('rule', kSpecDiagnosticRules)
)
.fn(t => {
const diag = generateDiagnostic(t.params.type, 'info', t.params.rule);
const code = kValidLocations[t.params.location](diag);
let res = true;
if (t.params.type === 'directive') {
res = t.params.location === 'module';
} else {
res = t.params.location !== 'module';
}
if (res === false) {
t.expectCompileResult(true, kValidLocations[t.params.location](''));
}
t.expectCompileResult(res, code);
});
g.test('invalid_locations')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
.desc(`Tests invalid locations`)
.params(u =>
u
.combine('type', kDiagnosticTypes)
.combine('location', keysOf(kInvalidLocations))
.combine('rule', kSpecDiagnosticRules)
)
.fn(t => {
const diag = generateDiagnostic(t.params.type, 'info', t.params.rule);
t.expectCompileResult(true, kInvalidLocations[t.params.location](''));
t.expectCompileResult(false, kInvalidLocations[t.params.location](diag));
});
g.test('conflicting_directive')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
.desc(`Tests conflicts between directives`)
.params(u => u.combine('s1', kSpecDiagnosticSeverities).combine('s2', kSpecDiagnosticSeverities))
.fn(t => {
const d1 = generateDiagnostic('directive', t.params.s1, 'derivative_uniformity');
const d2 = generateDiagnostic('directive', t.params.s2, 'derivative_uniformity');
const code = `${kValidLocations['module'](d1)}\n${kValidLocations['module'](d2)}`;
t.expectCompileResult(t.params.s1 === t.params.s2, code);
});
g.test('duplicate_attribute_same_location')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
.desc(`Tests duplicate diagnostics at the same location must be on different rules`)
.params(u =>
u
.combine('loc', keysOf(kValidLocations))
.combine('same_rule', [true, false] as const)
.beginSubcases()
.combine('s1', kSpecDiagnosticSeverities)
.combine('s2', kSpecDiagnosticSeverities)
.filter(u => {
return u.loc !== 'module';
})
)
.fn(t => {
const rule1 = 'derivative_uniformity';
const rule2 = 'another_diagnostic_rule';
const d1 = generateDiagnostic('attribute', t.params.s1, rule1);
const d2 = generateDiagnostic('attribute', t.params.s2, t.params.same_rule ? rule1 : rule2);
const code = `${kValidLocations[t.params.loc](`${d1} ${d2}`)}`;
t.expectCompileResult(!t.params.same_rule, code);
});
g.test('conflicting_attribute_different_location')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
.desc(`Tests conflicts between attributes`)
.params(u =>
u
.combine('loc', keysOf(kNestedLocations))
.combine('s1', kSpecDiagnosticSeverities)
.combine('s2', kSpecDiagnosticSeverities)
.filter(u => {
return u.s1 !== u.s2;
})
)
.fn(t => {
const d1 = generateDiagnostic('attribute', t.params.s1, 'derivative_uniformity');
const d2 = generateDiagnostic('attribute', t.params.s2, 'derivative_uniformity');
const code = `${kNestedLocations[t.params.loc](d1, d2)}`;
t.expectCompileResult(true, code);
});
g.test('after_other_directives')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
.desc(`Tests other global directives before a diagnostic directive.`)
.params(u =>
u.combine('directive', ['enable f16', 'requires readonly_and_readwrite_storage_textures'])
)
.fn(t => {
if (t.params.directive.startsWith('requires')) {
t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
}
let code = `${t.params.directive};`;
code += generateDiagnostic('directive', 'info', 'derivative_uniformity') + ';';
t.expectCompileResult(true, code);
});
interface ScopeCase {
code: string;
result: boolean | 'warn';
}
function scopeCode(body: string): string {
return `
@group(0) @binding(0) var t : texture_1d<f32>;
@group(0) @binding(1) var s : sampler;
var<private> non_uniform_cond : bool;
var<private> non_uniform_coord : f32;
var<private> non_uniform_val : u32;
@fragment fn main() {
${body}
}
`;
}
const kScopeCases: Record<string, ScopeCase> = {
override_global_off: {
code: `
${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
${scopeCode(`
${generateDiagnostic('', 'off', 'derivative_uniformity')}
if non_uniform_cond {
_ = textureSample(t,s,0.0);
}`)};
`,
result: true,
},
override_global_on: {
code: `
${generateDiagnostic('directive', 'off', 'derivative_uniformity')};
${scopeCode(`
${generateDiagnostic('', 'error', 'derivative_uniformity')}
if non_uniform_cond {
_ = textureSample(t,s,0.0);
}`)}
`,
result: false,
},
override_global_warn: {
code: `
${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
${scopeCode(`
${generateDiagnostic('', 'warning', 'derivative_uniformity')}
if non_uniform_cond {
_ = textureSample(t,s,0.0);
}`)}
`,
result: 'warn',
},
global_if_nothing_else_warn: {
code: `
${generateDiagnostic('directive', 'warning', 'derivative_uniformity')};
${scopeCode(`
if non_uniform_cond {
_ = textureSample(t,s,0.0);
}`)}
`,
result: 'warn',
},
deepest_nesting_warn: {
code: scopeCode(`
${generateDiagnostic('', 'error', 'derivative_uniformity')}
if non_uniform_cond {
${generateDiagnostic('', 'warning', 'derivative_uniformity')}
if non_uniform_cond {
_ = textureSample(t,s,0.0);
}
}`),
result: 'warn',
},
deepest_nesting_off: {
code: scopeCode(`
${generateDiagnostic('', 'error', 'derivative_uniformity')}
if non_uniform_cond {
${generateDiagnostic('', 'off', 'derivative_uniformity')}
if non_uniform_cond {
_ = textureSample(t,s,0.0);
}
}`),
result: true,
},
deepest_nesting_error: {
code: scopeCode(`
${generateDiagnostic('', 'off', 'derivative_uniformity')}
if non_uniform_cond {
${generateDiagnostic('', 'error', 'derivative_uniformity')}
if non_uniform_cond {
_ = textureSample(t,s,0.0);
}
}`),
result: false,
},
other_nest_unaffected: {
code: `
${generateDiagnostic('directive', 'warning', 'derivative_uniformity')};
${scopeCode(`
${generateDiagnostic('', 'off', 'derivative_uniformity')}
if non_uniform_cond {
_ = textureSample(t,s,0.0);
}
if non_uniform_cond {
_ = textureSample(t,s,0.0);
}`)}
`,
result: 'warn',
},
deeper_nest_no_effect: {
code: `
${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
${scopeCode(`
if non_uniform_cond {
${generateDiagnostic('', 'off', 'derivative_uniformity')}
if non_uniform_cond {
}
_ = textureSample(t,s,0.0);
}`)}
`,
result: false,
},
call_unaffected_error: {
code: `
${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
fn foo() { _ = textureSample(t,s,0.0); }
${scopeCode(`
${generateDiagnostic('', 'off', 'derivative_uniformity')}
if non_uniform_cond {
foo();
}`)}
`,
result: false,
},
call_unaffected_warn: {
code: `
${generateDiagnostic('directive', 'warning', 'derivative_uniformity')};
fn foo() { _ = textureSample(t,s,0.0); }
${scopeCode(`
${generateDiagnostic('', 'off', 'derivative_uniformity')}
if non_uniform_cond {
foo();
}`)}
`,
result: 'warn',
},
call_unaffected_off: {
code: `
${generateDiagnostic('directive', 'off', 'derivative_uniformity')};
fn foo() { _ = textureSample(t,s,0.0); }
${scopeCode(`
${generateDiagnostic('', 'error', 'derivative_uniformity')}
if non_uniform_cond {
foo();
}`)}
`,
result: true,
},
if_condition_error: {
code: scopeCode(`
if (non_uniform_cond) {
${generateDiagnostic('', 'error', 'derivative_uniformity')}
if textureSample(t,s,non_uniform_coord).x > 0.0
${generateDiagnostic('', 'off', 'derivative_uniformity')} {
}
}`),
result: false,
},
if_condition_warn: {
code: scopeCode(`
if non_uniform_cond {
${generateDiagnostic('', 'warning', 'derivative_uniformity')}
if textureSample(t,s,non_uniform_coord).x > 0.0
${generateDiagnostic('', 'error', 'derivative_uniformity')} {
}
}`),
result: 'warn',
},
if_condition_off: {
code: scopeCode(`
if non_uniform_cond {
${generateDiagnostic('', 'off', 'derivative_uniformity')}
if textureSample(t,s,non_uniform_coord).x > 0.0
${generateDiagnostic('', 'error', 'derivative_uniformity')} {
}
}`),
result: true,
},
switch_error: {
code: scopeCode(`
${generateDiagnostic('', 'error', 'derivative_uniformity')}
switch non_uniform_val {
case 0 ${generateDiagnostic('', 'off', 'derivative_uniformity')} {
}
default {
_ = textureSample(t,s,0.0);
}
}`),
result: false,
},
switch_warn: {
code: scopeCode(`
${generateDiagnostic('', 'warning', 'derivative_uniformity')}
switch non_uniform_val {
case 0 ${generateDiagnostic('', 'off', 'derivative_uniformity')} {
}
default {
_ = textureSample(t,s,0.0);
}
}`),
result: 'warn',
},
switch_off: {
code: scopeCode(`
${generateDiagnostic('', 'off', 'derivative_uniformity')}
switch non_uniform_val {
case 0 ${generateDiagnostic('', 'error', 'derivative_uniformity')}{
}
default {
_ = textureSample(t,s,0.0);
}
}`),
result: true,
},
};
g.test('diagnostic_scoping')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
.desc('Tests that innermost scope controls the diagnostic')
.params(u => u.combine('case', keysOf(kScopeCases)))
.fn(t => {
const testcase = kScopeCases[t.params.case];
if (testcase.result === 'warn') {
t.expectCompileWarning(true, testcase.code);
} else {
t.expectCompileResult(testcase.result as boolean, testcase.code);
}
});