export const description = ` Validation tests for let declarations `; 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); interface Case { code: string; valid: boolean | 'texture_and_sampler_let' | 'buffer_view'; decls?: string; } const kTypeCases: Record = { bool: { code: `let x : bool = true;`, valid: true, }, i32: { code: `let x : i32 = 1i;`, valid: true, }, u32: { code: `let x : u32 = 1u;`, valid: true, }, f32: { code: `let x : f32 = 1f;`, valid: true, }, f16: { code: `let x : f16 = 1h;`, valid: true, }, vec2i: { code: `let x : vec2i = vec2i();`, valid: true, }, vec3u: { code: `let x : vec3u = vec3u();`, valid: true, }, vec4f: { code: `let x : vec4f = vec4f();`, valid: true, }, mat2x2: { code: `let x : mat2x2f = mat2x2f();`, valid: true, }, mat4x3f: { code: `let x : mat4x3 = mat4x3();`, valid: true, }, array_sized: { code: `let x : array = array(1,2,3,4);`, valid: true, }, array_runtime: { code: `let x : array = array(1,2,3);`, valid: false, }, struct: { code: `let x : S = S(0);`, valid: true, decls: `struct S { x : u32 }`, }, atomic: { code: `let x : atomic = 0;`, valid: false, }, ptr_function: { code: ` var x : i32; let y : ptr = &x;`, valid: true, }, ptr_storage: { code: `let y : ptr = &x[0];`, valid: true, decls: `@group(0) @binding(0) var x : array;`, }, load_rule: { code: ` var x : i32 = 1; let y : i32 = x;`, valid: true, }, texture_2d: { code: `let x = tex2d;`, valid: 'texture_and_sampler_let', decls: `@group(0) @binding(0) var tex2d : texture_2d;`, }, texture_storage_1d: { code: `let x : texture_storage_1d = tex1d;`, valid: 'texture_and_sampler_let', decls: `@group(0) @binding(0) var tex1d : texture_storage_1d;`, }, sampler: { code: `let s = samp;`, valid: 'texture_and_sampler_let', decls: `@group(0) @binding(0) var samp : sampler;`, }, sampler_comparison: { code: `let s : sampler_comparison = samp_comp;`, valid: 'texture_and_sampler_let', decls: `@group(0) @binding(0) var samp_comp : sampler_comparison;`, }, buffer: { code: `let x : buffer = b;`, valid: false, decls: `@group(0) @binding(0) var b : buffer;`, }, buffer_sized: { code: `let x : buffer<128> = b;`, valid: false, decls: `@group(0) @binding(0) var b : buffer<128>;`, }, buffer_override_sized: { code: `let x : buffer = b;`, valid: false, decls: `override o = 16u; var b : buffer;`, }, ptr_buffer: { code: `let x : ptr = &b;`, valid: 'buffer_view', decls: `@group(0) @binding(0) var b : buffer;`, }, ptr_buffer_sized: { code: `let x : ptr> = &b;`, valid: 'buffer_view', decls: `@group(0) @binding(0) var b : buffer<128>;`, }, ptr_buffer_override_sized: { code: `let x : ptr> = &b;`, valid: 'buffer_view', decls: `override o = 16u; var b : buffer;`, }, }; g.test('type') .desc('Test let types') .params(u => u.combine('case', keysOf(kTypeCases))) .fn(t => { if (t.params.case === 'f16') { t.skipIfDeviceDoesNotHaveFeature('shader-f16'); } const testcase = kTypeCases[t.params.case]; const code = ` ${t.params.case === 'f16' ? 'enable f16;' : ''} ${testcase.decls ?? ''} fn foo() { ${testcase.code} }`; let expect: boolean = testcase.valid === true; if (testcase.valid === 'texture_and_sampler_let') { expect = t.hasLanguageFeature('texture_and_sampler_let'); } else if (testcase.valid === 'buffer_view') { expect = t.hasLanguageFeature('buffer_view'); } t.expectCompileResult(expect, code); }); const kInitCases: Record = { no_init: { code: `let x : u32;`, valid: false, }, no_type: { code: `let x = 1;`, valid: true, }, init_matching_type: { code: `let x : u32 = 1u;`, valid: true, }, init_mismatch_type: { code: `let x : u32 = 1i;`, valid: false, }, ptr_type_mismatch: { code: `var x : i32;\nlet y : ptr = &x;`, valid: false, }, ptr_access_mismatch: { code: `let y : ptr = &x;`, valid: false, decls: `@group(0) @binding(0) var x : u32;`, }, ptr_addrspace_mismatch: { code: `let y = ptr = &x;`, valid: false, decls: `@group(0) @binding(0) var x : u32;`, }, init_const_expr: { code: `let y = x * 2;`, valid: true, decls: `const x = 1;`, }, init_override_expr: { code: `let y = x + 1;`, valid: true, decls: `override x = 1;`, }, init_runtime_expr: { code: `var x = 1;\nlet y = x << 1;`, valid: true, }, buffer_mismatch: { code: `let x : ptr = &b;`, valid: false, decls: `@group(0) @binding(0) var b : buffer<128>;`, }, buffer_smaller_size: { code: `let x : ptr> = &b;`, valid: false, decls: `@group(0) @binding(0) var b : buffer<128>;`, }, buffer_mismatched_overrides: { code: `let p : ptr> = &b;`, valid: false, decls: `override o1 : u32;\noverride o2 : u32;\nvar b : buffer;`, }, }; g.test('initializer') .desc('Test let initializers') .params(u => u.combine('case', keysOf(kInitCases))) .fn(t => { const testcase = kInitCases[t.params.case]; const code = ` ${testcase.decls ?? ''} fn foo() { ${testcase.code} }`; const expect = testcase.valid; t.expectCompileResult(expect === true, code); }); g.test('module_scope') .desc('Test that let declarations are disallowed module scope') .fn(t => { const code = `let x = 0;`; t.expectCompileResult(false, code); }); const kTestTypes = [ 'f32', 'i32', 'u32', 'bool', 'vec2', 'vec2', 'vec2', 'vec2', 'vec3', 'vec3', 'vec3', 'vec3', 'vec4', 'vec4', 'vec4', 'vec4', 'mat2x2', 'mat2x3', 'mat2x4', 'mat3x2', 'mat3x3', 'mat3x4', 'mat4x2', 'mat4x3', 'mat4x4', // [1]: 12 is a random number here. find a solution to replace it. 'array', 'array', 'array', 'array', ] as const; g.test('initializer_type') .desc( ` If present, the initializer's type must match the store type of the variable. Testing scalars, vectors, and matrices of every dimension and type. TODO: add test for: structs - arrays of vectors and matrices - arrays of different length ` ) .params(u => u.beginSubcases().combine('lhsType', kTestTypes).combine('rhsType', kTestTypes)) .fn(t => { const { lhsType, rhsType } = t.params; const code = ` @fragment fn main() { let a : ${lhsType} = ${rhsType}(); } `; const expectation = lhsType === rhsType; t.expectCompileResult(expectation, code); });