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 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; @group(0) @binding(1) var s : sampler; var non_uniform_cond : bool; var non_uniform_coord : f32; var non_uniform_val : u32; @fragment fn main() { ${body} } `; } const kScopeCases: Record = { 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); } });