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; } 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: t === 'i32' || t === 'u32' || t === 'abstractInt', usesF16: scalarTypeOf(Type[t]).kind === 'f16', }, }), {} ), array: { value: 'array(1,2,3)', pass: false }, struct: { value: 'S(1,2)', pass: false, gdecl: 'struct S{ a:u32, b:u32}' }, atomic_u32: { value: 'xu', pass: false, gdecl: 'var xu: atomic;' }, atomic_i32: { value: 'xi', pass: false, gdecl: 'var xi: atomic;' }, } as const; g.test('var_init_type') .desc(`Test increment and decrement of a variable of various types`) .params(u => u.combine('type', keysOf(kConstructibleCases)).combine('direction', ['up', 'down'])) .fn(t => { const { value, pass, usesF16, gdecl } = kConstructibleCases[t.params.type] as Case; const operator = t.params.direction === 'up' ? '++' : '--'; const code = ` ${usesF16 ? 'enable f16;' : ''} ${gdecl ?? ''} fn f() { var a = ${value}; a${operator}; }`; t.expectCompileResult(pass, code); }); interface ComponentCase { type: string; wgsl: string; pass: boolean; usesF16?: boolean; gdecl?: string; } const kComponentCases = { v2u_x: { type: 'vec2u', wgsl: 'a.x', pass: true }, v2u_y: { type: 'vec2u', wgsl: 'a.y', pass: true }, v3u_x: { type: 'vec3u', wgsl: 'a.x', pass: true }, v3u_y: { type: 'vec3u', wgsl: 'a.y', pass: true }, v3u_z: { type: 'vec3u', wgsl: 'a.z', pass: true }, v4u_x: { type: 'vec4u', wgsl: 'a.x', pass: true }, v4u_y: { type: 'vec4u', wgsl: 'a.y', pass: true }, v4u_z: { type: 'vec4u', wgsl: 'a.z', pass: true }, v4u_w: { type: 'vec4u', wgsl: 'a.w', pass: true }, v2i_x: { type: 'vec2i', wgsl: 'a.x', pass: true }, v2i_y: { type: 'vec2i', wgsl: 'a.y', pass: true }, v3i_x: { type: 'vec3i', wgsl: 'a.x', pass: true }, v3i_y: { type: 'vec3i', wgsl: 'a.y', pass: true }, v3i_z: { type: 'vec3i', wgsl: 'a.z', pass: true }, v4i_x: { type: 'vec4i', wgsl: 'a.x', pass: true }, v4i_y: { type: 'vec4i', wgsl: 'a.y', pass: true }, v4i_z: { type: 'vec4i', wgsl: 'a.z', pass: true }, v4i_w: { type: 'vec4i', wgsl: 'a.w', pass: true }, v2u_xx: { type: 'vec2u', wgsl: 'a.xx', pass: false }, v2u_indexed: { type: 'vec2u', wgsl: 'a[0]', pass: true }, v2f_x: { type: 'vec2f', wgsl: 'a.x', pass: false }, v2h_x: { type: 'vec2h', wgsl: 'a.x', pass: false, usesF16: true }, mat2x2f: { type: 'mat2x2f', wgsl: 'a[0][0]', pass: false }, mat2x2h: { type: 'mat2x2h', wgsl: 'a[0][0]', pass: false, usesF16: true }, array: { type: 'array', wgsl: 'a', pass: false }, array_i: { type: 'array', wgsl: 'a[0]', pass: true }, array_f: { type: 'array', wgsl: 'a[0]', pass: false }, struct: { type: 'S', wgsl: 'S', pass: false, gdecl: 'struct S{field:i32}' }, struct_var: { type: 'S', wgsl: 'a', pass: false, gdecl: 'struct S{field:i32}' }, struct_field: { type: 'S', wgsl: 'a.field', pass: true, gdecl: 'struct S{field:i32}' }, } as const; g.test('component') .desc('Test increment and decrement of component of various types') .params(u => u.combine('type', keysOf(kComponentCases)).combine('direction', ['up', 'down'])) .fn(t => { const { type, wgsl, pass, usesF16, gdecl } = kComponentCases[t.params.type] as ComponentCase; const operator = t.params.direction === 'up' ? '++' : '--'; const code = ` ${usesF16 ? 'enable f16;' : ''} ${gdecl ?? ''} fn f() { var a: ${type}; ${wgsl}${operator}; }`; t.expectCompileResult(pass, code); }); interface ParseCase { // Statement in a function to execute. wgsl: string; // True if the test should pass pass: boolean; // Module-scope program fragment, if any. Usually a declaration gdecl?: string; } const kTests = { var: { wgsl: 'a++;', pass: true }, vector: { wgsl: 'v++;', pass: false }, paren_var_paren: { wgsl: '(a)++;', pass: true }, star_and_var: { wgsl: '*&a++;', pass: true }, paren_star_and_var_paren: { wgsl: '(*&a)++;', pass: true }, many_star_and_var: { wgsl: '*&*&*&a++;', pass: true }, space: { wgsl: 'a ++;', pass: true }, tab: { wgsl: 'a\t++;', pass: true }, newline: { wgsl: 'a\n++;', pass: true }, cr: { wgsl: 'a\r++;', pass: true }, space_space: { wgsl: 'a ++ ;', pass: true }, plus_space_plus: { wgsl: 'a+ +;', pass: false }, minux_space_minus: { wgsl: 'a- -;', pass: false }, no_var: { wgsl: '++;', pass: false }, no_semi: { wgsl: 'a++', pass: false }, prefix: { wgsl: '++a;', pass: false }, postfix_x: { wgsl: 'v++.x;', pass: false }, postfix_r: { wgsl: 'v++.r;', pass: false }, postfix_index: { wgsl: 'v++[0];', pass: false }, postfix_field: { wgsl: 'a++.foo;', pass: false }, literal_i32: { wgsl: '12i++;', pass: false }, literal_u32: { wgsl: '12u++;', pass: false }, literal_abstract_int: { wgsl: '12++;', pass: false }, literal_abstract_float: { wgsl: '12.0++;', pass: false }, literal_f32: { wgsl: '12.0f++;', pass: false }, assign_to: { wgsl: 'a++ = 1;', pass: false }, at_global: { wgsl: '', pass: false, gdecl: 'var g:i32; g++;' }, private: { wgsl: 'g++;', pass: true, gdecl: 'var g:i32;' }, workgroup: { wgsl: 'g++;', pass: true, gdecl: 'var g:i32;' }, storage_rw: { wgsl: 'g++;', pass: true, gdecl: '@group(0) @binding(0) var g: i32;', }, storage_r: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var g: i32;', }, storage: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var g: i32;' }, uniform: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var g: i32;' }, texture: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var g: texture_2d;' }, texture_x: { wgsl: 'g.x++;', pass: false, gdecl: '@group(0) @binding(0) var g: texture_2d;', }, texture_storage: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var g: texture_storage_2d;', }, texture_storage_x: { wgsl: 'g.x++;', pass: false, gdecl: '@group(0) @binding(0) var g: texture_storage_2d;', }, sampler: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var g: sampler;' }, sampler_comparison: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var g: sampler_comparison;', }, override: { wgsl: 'g++;', pass: false, gdecl: 'override g:i32;' }, global_const: { wgsl: 'g++;', pass: false, gdecl: 'const g:i32 = 0;' }, workgroup_atomic: { wgsl: 'g++;', pass: false, gdecl: 'var g:atomic;' }, storage_atomic: { wgsl: 'g++;', pass: false, gdecl: '@group(0) @binding(0) var g:atomic;', }, subexpr: { wgsl: 'a = b++;', pass: false }, expr_paren: { wgsl: '(a++);', pass: false }, expr_add: { wgsl: '0 + a++;', pass: false }, expr_negate: { wgsl: '-a++;', pass: false }, inc_inc: { wgsl: 'a++++;', pass: false }, inc_space_inc: { wgsl: 'a++ ++;', pass: false }, inc_dec: { wgsl: 'a++--;', pass: false }, inc_space_dec: { wgsl: 'a++ --;', pass: false }, paren_inc: { wgsl: '(a++)++;', pass: false }, paren_dec: { wgsl: '(a++)--;', pass: false }, in_block: { wgsl: '{ a++; }', pass: true }, in_for_init: { wgsl: 'for (a++;false;) {}', pass: true }, in_for_cond: { wgsl: 'for (;a++;) {}', pass: false }, in_for_update: { wgsl: 'for (;false;a++) {}', pass: true }, in_for_update_semi: { wgsl: 'for (;false;a++;) {}', pass: false }, in_continuing: { wgsl: 'loop { continuing { a++; break if true;}}', pass: true }, let: { wgsl: 'let c = a; c++;', pass: false }, const: { wgsl: 'const c = 1; c++;', pass: false }, builtin: { wgsl: 'max++', pass: false }, enum: { wgsl: 'r32uint++', pass: false }, param: { wgsl: '', pass: false, gdecl: 'fn bump(p: i32) { p++;}' }, }; g.test('parse') .desc(`Test that increment and decrement statements are parsed correctly.`) .params(u => u.combine('test', keysOf(kTests)).combine('direction', ['up', 'down'])) .fn(t => { const c = kTests[t.params.test] as ParseCase; let { wgsl, gdecl } = c; gdecl = gdecl ?? ''; if (t.params.direction === 'down') { wgsl = wgsl.replace('++', '--'); gdecl = gdecl.replace('++', '--'); } const code = ` ${gdecl} fn f() { var a: u32; var b: u32; var v: vec4u; ${wgsl} }`; t.expectCompileResult(c.pass, code); });