| export const description = `Validation for phony assignment statements`; |
| |
| import { makeTestGroup } from '../../../../common/framework/test_group.js'; |
| import { keysOf } from '../../../../common/util/data_tables.js'; |
| import { scalarTypeOf, Type } from '../../../util/conversion.js'; |
| import { ShaderValidationTest } from '../shader_validation_test.js'; |
| |
| export const g = makeTestGroup(ShaderValidationTest); |
| |
| interface Case { |
| value: string; |
| pass: boolean; |
| usesF16?: boolean; |
| gdecl?: string; |
| ldecl?: string; |
| } |
| |
| const kConstructibleTypes = [ |
| 'bool', |
| 'i32', |
| 'u32', |
| 'f32', |
| 'f16', |
| 'vec2f', |
| 'vec3h', |
| 'vec4u', |
| 'vec3b', |
| 'mat2x3f', |
| 'mat4x2h', |
| 'abstractInt', |
| 'abstractFloat', |
| ] as const; |
| |
| const kConstructibleCases = { |
| ...kConstructibleTypes.reduce( |
| (acc, t) => ({ |
| ...acc, |
| [t]: { |
| value: Type[t].create(1).wgsl(), |
| pass: true, |
| usesF16: scalarTypeOf(Type[t]).kind === 'f16', |
| }, |
| }), |
| {} |
| ), |
| array: { value: 'array(1,2,3)', pass: true }, |
| struct: { value: 'S(1,2)', pass: true, gdecl: 'struct S{ a:u32, b:u32}' }, |
| atomic_u32: { value: 'xu', pass: false, gdecl: 'var<workgroup> xu: atomic<u32>;' }, |
| atomic_i32: { value: 'xi', pass: false, gdecl: 'var<workgroup> xi: atomic<i32>;' }, |
| } as const; |
| |
| g.test('rhs_constructible') |
| .desc(`Test that the rhs of 'phony assignment' can be a constructible.`) |
| .params(u => u.combine('type', keysOf(kConstructibleCases))) |
| .fn(t => { |
| const { value, pass, usesF16, gdecl } = kConstructibleCases[t.params.type] as Case; |
| const code = ` |
| ${usesF16 ? 'enable f16;' : ''} |
| ${gdecl ?? ''} |
| fn f() { |
| _ = ${value}; |
| }`; |
| t.expectCompileResult(pass, code); |
| }); |
| |
| const kVarCases = { |
| storage: { value: 'x', gdecl: '@group(0) @binding(0) var<storage> x: array<u32,1>;', pass: true }, |
| storage_unsized: { |
| value: 'x', |
| gdecl: '@group(0) @binding(0) var<storage> x: array<u32>;', |
| pass: false, |
| }, |
| storage_atomic: { |
| value: 'x', |
| gdecl: '@group(0) @binding(0) var<storage,read_write> x: atomic<u32>;', |
| pass: false, |
| }, |
| uniform: { value: 'x', gdecl: '@group(0) @binding(0) var<uniform> x: u32;', pass: true }, |
| texture: { value: 'x', gdecl: '@group(0) @binding(0) var x: texture_2d<f32>;', pass: true }, |
| sampler: { value: 'x', gdecl: '@group(0) @binding(0) var x: sampler;', pass: true }, |
| sampler_comparison: { |
| value: 'x', |
| gdecl: '@group(0) @binding(0) var x: sampler_comparison;', |
| pass: true, |
| }, |
| private: { value: 'x', gdecl: 'var<private> x: u32;', pass: true }, |
| workgroup: { value: 'x', gdecl: 'var<workgroup> x: u32;', pass: true }, |
| workgroup_atomic: { value: 'x', gdecl: 'var<workgroup> x: atomic<u32>;', pass: false }, |
| override: { value: 'o', gdecl: 'override o: u32;', pass: true }, |
| function_var: { value: 'x', ldecl: 'var x: u32;', pass: true }, |
| let: { value: 'v', ldecl: 'let v = 1;', pass: true }, |
| const: { value: 'c', gdecl: 'const c = 1;', pass: true }, |
| function_const: { value: 'c', ldecl: 'const c = 1;', pass: true }, |
| ptr: { value: '&x', ldecl: 'var x: u32;', pass: true }, |
| ptr_to_unsized: { |
| value: '&x', |
| gdecl: '@group(0) @binding(0) var<storage> x: array<u32>;', |
| pass: true, |
| }, |
| indexed: { |
| value: 'x[0]', |
| gdecl: '@group(0) @binding(0) var<storage> x: array<u32>;', |
| pass: true, |
| }, |
| user_fn: { value: 'f', pass: false }, |
| builtin: { value: 'max', pass: false }, |
| builtin_call: { value: 'max(1,1)', pass: true }, |
| user_call: { value: 'callee()', pass: true, gdecl: 'fn callee() -> i32 { return 0; }' }, |
| undeclared: { value: 'does_not_exist', pass: false }, |
| } as const; |
| |
| g.test('rhs_with_decl') |
| .desc(`Test rhs of 'phony assignment' involving declared objects.`) |
| .params(u => u.combine('test', keysOf(kVarCases))) |
| .fn(t => { |
| const { value, pass, gdecl, ldecl } = kVarCases[t.params.test] as Case; |
| const code = ` |
| ${gdecl ?? ''} |
| @compute @workgroup_size(1) |
| fn f() { |
| ${ldecl ?? ''} |
| _ = ${value}; |
| }`; |
| t.expectCompileResult(pass, code); |
| }); |
| |
| const kTests = { |
| literal: { wgsl: `_ = 1;`, pass: true }, |
| expr: { wgsl: `_ = (1+v);`, pass: true }, |
| var: { wgsl: `_ = v;`, pass: true }, |
| |
| in_for_init: { wgsl: `for (_ = v;false;) {}`, pass: true }, |
| in_for_init_semi: { wgsl: `for (_ = v;;false;) {}`, pass: false }, |
| in_for_update: { wgsl: `for (;false; _ = v) {}`, pass: true }, |
| in_for_update_semi: { wgsl: `for (;false; _ = v;) {}`, pass: false }, |
| |
| in_block: { wgsl: `{_ = v;}`, pass: true }, |
| in_continuing: { wgsl: `loop { continuing { _ = v; break if true;}}`, pass: true }, |
| |
| in_paren: { wgsl: `(_ = v;)`, pass: false }, |
| |
| underscore: { wgsl: `_`, pass: false }, |
| underscore_semi: { wgsl: `_;`, pass: false }, |
| underscore_equal: { wgsl: `_=`, pass: false }, |
| underscore_equal_semi: { wgsl: `_=;`, pass: false }, |
| underscore_equal_underscore_semi: { wgsl: `_=_;`, pass: false }, |
| paren_underscore_paren: { wgsl: `(_) = 1;`, pass: false }, |
| // LHS is not a reference type |
| star_ampersand_undsscore: { wgsl: `*&_ = 1;`, pass: false }, |
| compound: { wgsl: `_ += 1;`, pass: false }, |
| equality: { wgsl: `_ == 1;`, pass: false }, |
| block: { wgsl: `_ = {};`, pass: false }, |
| return: { wgsl: `_ = return;`, pass: false }, |
| }; |
| |
| g.test('parse') |
| .desc(`Test that 'phony assignment' statements are parsed correctly.`) |
| .params(u => u.combine('test', keysOf(kTests))) |
| .fn(t => { |
| const code = ` |
| fn f() { |
| var v: u32; |
| ${kTests[t.params.test].wgsl} |
| }`; |
| t.expectCompileResult(kTests[t.params.test].pass, code); |
| }); |
| |
| g.test('module_scope') |
| .desc(`Phony assignment is not valid at module scope`) |
| .fn(t => { |
| const code = `_ = 1; `; |
| t.expectCompileResult(false, code); |
| }); |