export const description = `Validation tests for function restrictions`; 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 VertexPosCase { name: string; value: string; valid: boolean; } const kCCommonTypeDecls = ` struct runtime_array_struct { arr : array } struct constructible { a : i32, b : u32, c : f32, d : bool, } struct host_shareable { a : i32, b : u32, c : f32, } struct struct_with_array { a : array } override override_no_default : u32; override override_default = 4u; override override_expr = override_default + 2; `; const kVertexPosCases: Record = { bare_position: { name: `@builtin(position) vec4f`, value: `vec4f()`, valid: true }, nested_position: { name: `pos_struct`, value: `pos_struct()`, valid: true }, no_bare_position: { name: `vec4f`, value: `vec4f()`, valid: false }, no_nested_position: { name: `no_pos_struct`, value: `no_pos_struct()`, valid: false }, }; g.test('vertex_returns_position') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction') .desc(`Test that a vertex shader should return position`) .params(u => u.combine('case', keysOf(kVertexPosCases))) .fn(t => { const testcase = kVertexPosCases[t.params.case]; const code = ` struct pos_struct { @builtin(position) pos : vec4f } struct no_pos_struct { @location(0) x : vec4f } @vertex fn main() -> ${testcase.name} { return ${testcase.value}; }`; t.expectCompileResult(testcase.valid, code); }); g.test('entry_point_call_target') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction') .desc(`Test that an entry point cannot be the target of a function call`) .params(u => u .combine('stage', ['@fragment', '@vertex', '@compute @workgroup_size(1,1,1)'] as const) .combine('entry_point', ['with', 'without'] as const) ) .fn(t => { const use_attr = t.params.entry_point === 'with'; let ret_attr = ''; if (use_attr && t.params.stage === '@vertex') { ret_attr = '@builtin(position)'; } const ret = t.params.stage.indexOf('@vertex') === 0 ? `-> ${ret_attr} vec4f` : ''; const ret_value = t.params.stage.indexOf('@vertex') === 0 ? `return vec4f();` : ''; const call = t.params.stage.indexOf('@vertex') === 0 ? 'let tmp = bar();' : 'bar();'; const stage_attr = use_attr ? t.params.stage : ''; const code = ` ${stage_attr} fn bar() ${ret} { ${ret_value} } fn foo() { ${call} } `; t.expectCompileResult(!use_attr, code); }); interface RetTypeCase { name: string; value: string; valid: boolean; } const kFunctionRetTypeCases: Record = { // Constructible types, u32: { name: `u32`, value: ``, valid: true }, i32: { name: `i32`, value: ``, valid: true }, f32: { name: `f32`, value: ``, valid: true }, bool: { name: `bool`, value: ``, valid: true }, f16: { name: `f16`, value: ``, valid: true }, vec2: { name: `vec2u`, value: ``, valid: true }, vec3: { name: `vec3i`, value: ``, valid: true }, vec4: { name: `vec4f`, value: ``, valid: true }, mat2x2: { name: `mat2x2f`, value: ``, valid: true }, mat2x3: { name: `mat2x3f`, value: ``, valid: true }, mat2x4: { name: `mat2x4f`, value: ``, valid: true }, mat3x2: { name: `mat3x2f`, value: ``, valid: true }, mat3x3: { name: `mat3x3f`, value: ``, valid: true }, mat3x4: { name: `mat3x4f`, value: ``, valid: true }, mat4x2: { name: `mat4x2f`, value: ``, valid: true }, mat4x3: { name: `mat4x3f`, value: ``, valid: true }, mat4x4: { name: `mat4x4f`, value: ``, valid: true }, array1: { name: `array`, value: ``, valid: true }, array2: { name: `array`, value: ``, valid: true }, array3: { name: `array`, value: ``, valid: true }, array4: { name: `array`, value: ``, valid: true }, array5: { name: `array`, value: ``, valid: true }, struct1: { name: `constructible`, value: ``, valid: true }, struct2: { name: `struct_with_array`, value: ``, valid: true }, // Non-constructible types. runtime_array: { name: `array`, value: ``, valid: false }, runtime_struct: { name: `runtime_array_struct`, value: ``, valid: false }, override_array: { name: `array`, value: ``, valid: false }, atomic_u32: { name: `atomic`, value: `atomic_wg`, valid: false }, atomic_struct: { name: `atomic_struct`, value: ``, valid: false }, texture_sample: { name: `texture_2d`, value: `t`, valid: false }, texture_depth: { name: `texture_depth_2d`, value: `t_depth`, valid: false }, texture_multisampled: { name: `texture_multisampled_2d`, value: `t_multisampled`, valid: false, }, texture_storage: { name: `texture_storage_2d`, value: `t_storage`, valid: false, }, sampler: { name: `sampler`, value: `s`, valid: false }, sampler_comparison: { name: `sampler_comparison`, value: `s_depth`, valid: false }, ptr: { name: `ptr>`, value: `&atomic_wg`, valid: false }, }; g.test('function_return_types') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction') .desc(`Test that function return types must be constructible`) .params(u => u.combine('case', keysOf(kFunctionRetTypeCases))) .fn(t => { const testcase = kFunctionRetTypeCases[t.params.case]; const enable = testcase.name === 'f16' ? 'enable f16;' : ''; const value = testcase.value === '' ? `${testcase.name}()` : testcase.value; const code = ` ${enable} ${kCCommonTypeDecls} struct atomic_struct { a : atomic }; override override_size : u32; var atomic_wg : atomic; @group(0) @binding(0) var t : texture_2d; @group(0) @binding(1) var s : sampler; @group(0) @binding(2) var s_depth : sampler_comparison; @group(0) @binding(3) var t_storage : texture_storage_2d; @group(0) @binding(4) var t_depth : texture_depth_2d; @group(0) @binding(5) var t_multisampled : texture_multisampled_2d; @group(0) @binding(6) var t_external : texture_external; fn foo() -> ${testcase.name} { return ${value}; }`; t.expectCompileResult(testcase.valid, code); }); interface ParamTypeCase { name: string; valid: boolean | 'with_unrestricted_pointer_parameters' | 'with_buffer_view'; } const kFunctionParamTypeCases: Record = { // Constructible types, u32: { name: `u32`, valid: true }, i32: { name: `i32`, valid: true }, f32: { name: `f32`, valid: true }, bool: { name: `bool`, valid: true }, f16: { name: `f16`, valid: true }, vec2: { name: `vec2u`, valid: true }, vec3: { name: `vec3i`, valid: true }, vec4: { name: `vec4f`, valid: true }, mat2x2: { name: `mat2x2f`, valid: true }, mat2x3: { name: `mat2x3f`, valid: true }, mat2x4: { name: `mat2x4f`, valid: true }, mat3x2: { name: `mat3x2f`, valid: true }, mat3x3: { name: `mat3x3f`, valid: true }, mat3x4: { name: `mat3x4f`, valid: true }, mat4x2: { name: `mat4x2f`, valid: true }, mat4x3: { name: `mat4x3f`, valid: true }, mat4x4: { name: `mat4x4f`, valid: true }, array1: { name: `array`, valid: true }, array2: { name: `array`, valid: true }, array3: { name: `array`, valid: true }, array4: { name: `array`, valid: true }, array5: { name: `array`, valid: true }, struct1: { name: `constructible`, valid: true }, struct2: { name: `struct_with_array`, valid: true }, // Non-constructible types. runtime_array: { name: `array`, valid: false }, runtime_struct: { name: `runtime_array_struct`, valid: false }, override_array: { name: `array`, valid: false }, atomic_u32: { name: `atomic`, valid: false }, atomic_struct: { name: `atomic_struct`, valid: false }, // Textures and samplers. texture_sample: { name: `texture_2d`, valid: true }, texture_depth: { name: `texture_depth_2d`, valid: true }, texture_multisampled: { name: `texture_multisampled_2d`, valid: true, }, texture_storage: { name: `texture_storage_2d`, valid: true }, sampler: { name: `sampler`, valid: true }, sampler_comparison: { name: `sampler_comparison`, valid: true }, // Valid pointers. ptr1: { name: `ptr`, valid: true }, ptr2: { name: `ptr`, valid: true }, ptr3: { name: `ptr`, valid: true }, ptr4: { name: `ptr`, valid: true }, // Pointers only valid with unrestricted_pointer_parameters ptr5: { name: `ptr`, valid: 'with_unrestricted_pointer_parameters' }, ptr6: { name: `ptr`, valid: 'with_unrestricted_pointer_parameters' }, ptr7: { name: `ptr`, valid: 'with_unrestricted_pointer_parameters' }, ptr8: { name: `ptr`, valid: 'with_unrestricted_pointer_parameters' }, ptr9: { name: `ptr`, valid: 'with_unrestricted_pointer_parameters' }, ptr10: { name: `ptr`, valid: 'with_unrestricted_pointer_parameters', }, ptr11: { name: `ptr`, valid: 'with_unrestricted_pointer_parameters', }, ptr12: { name: `ptr`, valid: 'with_unrestricted_pointer_parameters', }, ptrWorkgroupAtomic: { name: `ptr>`, valid: 'with_unrestricted_pointer_parameters', }, ptrWorkgroupNestedAtomic: { name: `ptr,1>>`, valid: 'with_unrestricted_pointer_parameters', }, ptrWorkgroupOverrideNoDefault: { name: `ptr>`, valid: 'with_unrestricted_pointer_parameters', }, ptrWorkgroupOverrideWithDefault: { name: `ptr>`, valid: 'with_unrestricted_pointer_parameters', }, ptrWorkgroupOverrideExpr: { name: `ptr>`, valid: 'with_unrestricted_pointer_parameters', }, // Invalid pointers. invalid_ptr1: { name: `ptr`, valid: false }, // Can't spell handle address space invalid_ptr2: { name: `ptr`, valid: false }, invalid_ptr3: { name: `ptr`, valid: false }, // No store type invalid_ptr4: { name: `ptr`, valid: false }, // Can't specify access mode invalid_ptr5: { name: `ptr`, valid: false }, // Can't specify access mode invalid_ptr6: { name: `ptr`, valid: false }, // Can't specify access mode invalid_ptr7: { name: `ptr`, valid: false }, // Invalid store type invalid_ptr8: { name: `ptr`, valid: false }, // non-constructible pointer type // Buffers (need buffer_view and unrestricted_pointer_parameters) ptrBufferUnsized_storage: { name: `ptr`, valid: 'with_buffer_view', }, ptrBufferSized_storage: { name: `ptr, read_write>`, valid: 'with_buffer_view', }, ptrBufferSizedSmall_storage: { name: `ptr, read_write>`, valid: 'with_buffer_view', }, ptrBufferUnsized_ro_storage: { name: `ptr`, valid: 'with_buffer_view', }, ptrBufferSized_ro_storage: { name: `ptr>`, valid: 'with_buffer_view', }, ptrBufferSizedSmall_ro_storage: { name: `ptr>`, valid: 'with_buffer_view', }, ptrBufferUnsized_uniform: { name: `ptr`, valid: 'with_buffer_view', }, ptrBufferSized_uniform: { name: `ptr>`, valid: 'with_buffer_view', }, ptrBufferSizedSmall_uniform: { name: `ptr>`, valid: 'with_buffer_view', }, ptrBufferUnsized_workgroup: { name: `ptr`, valid: 'with_buffer_view', }, ptrBufferSized_workgroup: { name: `ptr>`, valid: 'with_buffer_view', }, ptrBufferSizedSmall_workgroup: { name: `ptr>`, valid: 'with_buffer_view', }, ptrBufferOverrideNoDefault_workgroup: { name: `ptr>`, valid: 'with_buffer_view', }, ptrBufferOverrideDefault_workgroup: { name: `ptr>`, valid: 'with_buffer_view', }, ptrBufferOverrideExpr_workgroup: { name: `ptr>`, valid: 'with_buffer_view', }, }; g.test('function_parameter_types') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction') .desc(`Test validation of user-declared function parameter types`) .params(u => u.combine('case', keysOf(kFunctionParamTypeCases))) .fn(t => { const testcase = kFunctionParamTypeCases[t.params.case]; const enable = testcase.name === 'f16' ? 'enable f16;' : ''; const code = ` ${enable} ${kCCommonTypeDecls} fn foo(param : ${testcase.name}) { }`; let isValid = testcase.valid; if (isValid === 'with_unrestricted_pointer_parameters') { isValid = t.hasLanguageFeature('unrestricted_pointer_parameters'); } else if (isValid === 'with_buffer_view') { isValid = t.hasLanguageFeature('unrestricted_pointer_parameters') && t.hasLanguageFeature('buffer_view'); } t.expectCompileResult(isValid, code); }); interface ParamValueCase { value: string; matches: string[]; needsUnrestrictedPointerParameters?: boolean; needsBufferView?: boolean; } const kFunctionParamValueCases: Record = { // Values u32_literal: { value: `0u`, matches: ['u32'] }, i32_literal: { value: `0i`, matches: ['i32'] }, f32_literal: { value: `0f`, matches: ['f32'] }, bool_literal: { value: `false`, matches: ['bool'] }, abstract_int_literal: { value: `0`, matches: ['u32', 'i32', 'f32', 'f16'] }, abstract_float_literal: { value: `0.0`, matches: ['f32', 'f16'] }, vec2u_constructor: { value: `vec2u()`, matches: ['vec2'] }, vec2i_constructor: { value: `vec2i()`, matches: [] }, vec2f_constructor: { value: `vec2f()`, matches: [] }, vec2b_constructor: { value: `vec2()`, matches: [] }, vec3u_constructor: { value: `vec3u()`, matches: [] }, vec3i_constructor: { value: `vec3i()`, matches: ['vec3'] }, vec3f_constructor: { value: `vec3f()`, matches: [] }, vec3b_constructor: { value: `vec3()`, matches: [] }, vec4u_constructor: { value: `vec4u()`, matches: [] }, vec4i_constructor: { value: `vec4i()`, matches: [] }, vec4f_constructor: { value: `vec4f()`, matches: ['vec4'] }, vec4b_constructor: { value: `vec4()`, matches: [] }, vec2_abstract_int: { value: `vec2(0,0)`, matches: ['vec2'] }, vec2_abstract_float: { value: `vec2(0.0,0)`, matches: [] }, vec3_abstract_int: { value: `vec3(0,0,0)`, matches: ['vec3'] }, vec3_abstract_float: { value: `vec3(0.0,0,0)`, matches: [] }, vec4_abstract_int: { value: `vec4(0,0,0,0)`, matches: ['vec4'] }, vec4_abstract_float: { value: `vec4(0.0,0,0,0)`, matches: ['vec4'] }, mat2x2_constructor: { value: `mat2x2f()`, matches: ['mat2x2'] }, mat2x3_constructor: { value: `mat2x3f()`, matches: ['mat2x3'] }, mat2x4_constructor: { value: `mat2x4f()`, matches: ['mat2x4'] }, mat3x2_constructor: { value: `mat3x2f()`, matches: ['mat3x2'] }, mat3x3_constructor: { value: `mat3x3f()`, matches: ['mat3x3'] }, mat3x4_constructor: { value: `mat3x4f()`, matches: ['mat3x4'] }, mat4x2_constructor: { value: `mat4x2f()`, matches: ['mat4x2'] }, mat4x3_constructor: { value: `mat4x3f()`, matches: ['mat4x3'] }, mat4x4_constructor: { value: `mat4x4f()`, matches: ['mat4x4'] }, array1_constructor: { value: `array()`, matches: ['array1'] }, array2_constructor: { value: `array()`, matches: ['array2'] }, array3_constructor: { value: `array()`, matches: ['array3'] }, array4_constructor: { value: `array()`, matches: ['array4'] }, array5_constructor: { value: `array()`, matches: ['array5'] }, struct1_constructor: { value: `constructible()`, matches: ['struct1'] }, struct2_constructor: { value: `struct_with_array()`, matches: ['struct2'] }, // Variable references g_u32: { value: `g_u32`, matches: ['u32'] }, g_i32: { value: `g_i32`, matches: ['i32'] }, g_f32: { value: `g_f32`, matches: ['f32'] }, g_bool: { value: `g_bool`, matches: ['bool'] }, g_vec2: { value: `g_vec2`, matches: ['vec2'] }, g_vec3: { value: `g_vec3`, matches: ['vec3'] }, g_vec4: { value: `g_vec4`, matches: ['vec4'] }, g_mat2x2: { value: `g_mat2x2`, matches: ['mat2x2'] }, g_mat2x3: { value: `g_mat2x3`, matches: ['mat2x3'] }, g_mat2x4: { value: `g_mat2x4`, matches: ['mat2x4'] }, g_mat3x2: { value: `g_mat3x2`, matches: ['mat3x2'] }, g_mat3x3: { value: `g_mat3x3`, matches: ['mat3x3'] }, g_mat3x4: { value: `g_mat3x4`, matches: ['mat3x4'] }, g_mat4x2: { value: `g_mat4x2`, matches: ['mat4x2'] }, g_mat4x3: { value: `g_mat4x3`, matches: ['mat4x3'] }, g_mat4x4: { value: `g_mat4x4`, matches: ['mat4x4'] }, g_array1: { value: `g_array1`, matches: ['array1'] }, g_array2: { value: `g_array2`, matches: ['array2'] }, g_array3: { value: `g_array3`, matches: ['array3'] }, g_array4: { value: `g_array4`, matches: ['array4'] }, g_array5: { value: `g_array5`, matches: ['array5'] }, g_constructible: { value: `g_constructible`, matches: ['struct1'] }, g_struct_with_array: { value: `g_struct_with_array`, matches: ['struct2'] }, f_u32: { value: `f_u32`, matches: ['u32'] }, f_i32: { value: `f_i32`, matches: ['i32'] }, f_f32: { value: `f_f32`, matches: ['f32'] }, f_bool: { value: `f_bool`, matches: ['bool'] }, f_vec2: { value: `f_vec2`, matches: ['vec2'] }, f_vec3: { value: `f_vec3`, matches: ['vec3'] }, f_vec4: { value: `f_vec4`, matches: ['vec4'] }, f_mat2x2: { value: `f_mat2x2`, matches: ['mat2x2'] }, f_mat2x3: { value: `f_mat2x3`, matches: ['mat2x3'] }, f_mat2x4: { value: `f_mat2x4`, matches: ['mat2x4'] }, f_mat3x2: { value: `f_mat3x2`, matches: ['mat3x2'] }, f_mat3x3: { value: `f_mat3x3`, matches: ['mat3x3'] }, f_mat3x4: { value: `f_mat3x4`, matches: ['mat3x4'] }, f_mat4x2: { value: `f_mat4x2`, matches: ['mat4x2'] }, f_mat4x3: { value: `f_mat4x3`, matches: ['mat4x3'] }, f_mat4x4: { value: `f_mat4x4`, matches: ['mat4x4'] }, f_array1: { value: `f_array1`, matches: ['array1'] }, f_array2: { value: `f_array2`, matches: ['array2'] }, f_array3: { value: `f_array3`, matches: ['array3'] }, f_array4: { value: `f_array4`, matches: ['array4'] }, f_array5: { value: `f_array5`, matches: ['array5'] }, f_constructible: { value: `f_constructible`, matches: ['struct1'] }, f_struct_with_array: { value: `f_struct_with_array`, matches: ['struct2'] }, g_index_u32: { value: `g_constructible.b`, matches: ['u32'] }, g_index_i32: { value: `g_constructible.a`, matches: ['i32'] }, g_index_f32: { value: `g_constructible.c`, matches: ['f32'] }, g_index_bool: { value: `g_constructible.d`, matches: ['bool'] }, f_index_u32: { value: `f_constructible.b`, matches: ['u32'] }, f_index_i32: { value: `f_constructible.a`, matches: ['i32'] }, f_index_f32: { value: `f_constructible.c`, matches: ['f32'] }, f_index_bool: { value: `f_constructible.d`, matches: ['bool'] }, g_array_index_u32: { value: `g_struct_with_array.a[0].b`, matches: ['u32'] }, g_array_index_i32: { value: `g_struct_with_array.a[1].a`, matches: ['i32'] }, g_array_index_f32: { value: `g_struct_with_array.a[2].c`, matches: ['f32'] }, g_array_index_bool: { value: `g_struct_with_array.a[3].d`, matches: ['bool'] }, f_array_index_u32: { value: `f_struct_with_array.a[0].b`, matches: ['u32'] }, f_array_index_i32: { value: `f_struct_with_array.a[1].a`, matches: ['i32'] }, f_array_index_f32: { value: `f_struct_with_array.a[2].c`, matches: ['f32'] }, f_array_index_bool: { value: `f_struct_with_array.a[3].d`, matches: ['bool'] }, // Textures and samplers texture_sample: { value: `t`, matches: ['texture_sample'] }, texture_depth: { value: `t_depth`, matches: ['texture_depth'] }, texture_multisampled: { value: `t_multisampled`, matches: ['texture_multisampled'] }, texture_storage: { value: `t_storage`, matches: ['texture_storage'] }, texture_external: { value: `t_external`, matches: ['texture_external'] }, sampler: { value: `s`, matches: ['sampler'] }, sampler_comparison: { value: `s_depth`, matches: ['sampler_comparison'] }, // Pointers ptr1: { value: `&f_u32`, matches: ['ptr1'] }, ptr2: { value: `&f_constructible`, matches: ['ptr2'] }, ptr3: { value: `&g_u32`, matches: ['ptr3'] }, ptr4: { value: `&g_constructible`, matches: ['ptr4'] }, ptr_let1: { value: `ptr_f_u32`, matches: ['ptr1'] }, ptr_let2: { value: `ptr_f_constructible`, matches: ['ptr2'] }, ptr_let3: { value: `ptr_g_u32`, matches: ['ptr3'] }, ptr_let4: { value: `ptr_g_constructible`, matches: ['ptr4'] }, ptr_let5: { value: `let_let_f_u32`, matches: ['ptr1'] }, // Requires 'unrestricted_pointer_parameters' WGSL feature ptr5: { value: `&f_constructible.b`, matches: ['ptr1'], needsUnrestrictedPointerParameters: true, }, ptr6: { value: `&g_constructible.b`, matches: ['ptr3'], needsUnrestrictedPointerParameters: true, }, ptr7: { value: `&f_struct_with_array.a[1].b`, matches: ['ptr1'], needsUnrestrictedPointerParameters: true, }, ptr8: { value: `&g_struct_with_array.a[2]`, matches: ['ptr4'], needsUnrestrictedPointerParameters: true, }, ptr9: { value: `&ro_host_shareable.b`, matches: ['ptr5', 'ptr6'], needsUnrestrictedPointerParameters: true, }, ptr10: { value: `&rw_host_shareable`, matches: ['ptr10'], needsUnrestrictedPointerParameters: true, }, ptr11: { value: `&ro_host_shareable`, matches: ['ptr11'], needsUnrestrictedPointerParameters: true, }, ptr12: { value: `&uniform_host_shareable`, matches: ['ptr12'], needsUnrestrictedPointerParameters: true, }, ptrWorkgroupOverrideNoDefault: { value: `&wg_override_no_default`, matches: ['ptrWorkgroupOverrideNoDefault'], needsUnrestrictedPointerParameters: true, }, ptrWorkgroupOverrideWithDefault: { value: `&wg_override_default`, matches: ['ptrWorkgroupOverrideWithDefault'], needsUnrestrictedPointerParameters: true, }, ptrWorkgroupOverrideExpr: { value: `&wg_override_expr`, matches: ['ptrWorkgroupOverrideExpr'], needsUnrestrictedPointerParameters: true, }, // Buffer view ptrStorageBufferUnsized: { value: `&storage_buffer_unsized`, matches: ['ptrBufferUnsized_storage'], needsBufferView: true, }, ptrStorageBufferSized: { value: `&storage_buffer_sized`, matches: ['ptrBufferUnsized_storage', 'ptrBufferSized_storage', 'ptrBufferSizedSmall_storage'], needsBufferView: true, }, ptrStorageBufferSizedSmall: { value: `&storage_buffer_sized_small`, matches: ['ptrBufferUnsized_storage', 'ptrBufferSizedSmall_storage'], needsBufferView: true, }, ptrROStorageBufferUnsized: { value: `&ro_storage_buffer_unsized`, matches: ['ptrBufferUnsized_ro_storage'], needsBufferView: true, }, ptrROStorageBufferSized: { value: `&ro_storage_buffer_sized`, matches: [ 'ptrBufferUnsized_ro_storage', 'ptrBufferSized_ro_storage', 'ptrBufferSizedSmall_ro_storage', ], needsBufferView: true, }, ptrROStorageBufferSizedSmall: { value: `&ro_storage_buffer_sized_small`, matches: ['ptrBufferUnsized_ro_storage', 'ptrBufferSizedSmall_ro_storage'], needsBufferView: true, }, ptrUniformBufferSized: { value: `&uniform_buffer_sized`, matches: ['ptrBufferUnsized_uniform', 'ptrBufferSized_uniform', 'ptrBufferSizedSmall_uniform'], needsBufferView: true, }, ptrUniformBufferSizedSmall: { value: `&uniform_buffer_sized_small`, matches: ['ptrBufferUnsized_uniform', 'ptrBufferSizedSmall_uniform'], needsBufferView: true, }, ptrWorkgroupBufferSized: { value: `&wg_buffer_sized`, matches: [ 'ptrBufferUnsized_workgroup', 'ptrBufferSized_workgroup', 'ptrBufferSizedSmall_workgroup', ], needsBufferView: true, }, ptrWorkgroupBufferSizedSmall: { value: `&wg_buffer_sized_small`, matches: ['ptrBufferUnsized_workgroup', 'ptrBufferSizedSmall_workgroup'], needsBufferView: true, }, ptrWorkgroupBufferOverrideNoDefault: { value: `&wg_buffer_override_no_default`, matches: ['ptrBufferUnsized_workgroup', 'ptrBufferOverrideNoDefault_workgroup'], needsBufferView: true, }, ptrWorkgroupBufferOverrideDefault: { value: `&wg_buffer_override_default`, matches: ['ptrBufferUnsized_workgroup', 'ptrBufferOverrideDefault_workgroup'], needsBufferView: true, }, ptrWorkgroupBufferOverrideExpr: { value: `&wg_buffer_override_expr`, matches: ['ptrBufferUnsized_workgroup', 'ptrBufferOverrideExpr_workgroup'], needsBufferView: true, }, }; function parameterMatches(decl: string, matches: string[]): boolean { for (const val of matches) { if (decl === val) { return true; } } return false; } g.test('function_parameter_matching') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction') .desc( `Test that function parameter types match function parameter type on user-declared functions` ) .params(u => u .combine('decl', keysOf(kFunctionParamTypeCases)) .filter(u => { return kFunctionParamTypeCases[u.decl].valid !== false; }) .beginSubcases() .combine('arg', keysOf(kFunctionParamValueCases)) ) .fn(t => { const param = kFunctionParamTypeCases[t.params.decl]; const arg = kFunctionParamValueCases[t.params.arg]; const enable = param.name === 'f16' ? 'enable f16;' : ''; let buffer_decls = ``; if (param.valid === 'with_buffer_view') { buffer_decls = ` @group(2) @binding(0) var storage_buffer_unsized : buffer; @group(2) @binding(1) var storage_buffer_sized : buffer<128>; @group(2) @binding(2) var storage_buffer_sized_small : buffer<64>; @group(2) @binding(3) var ro_storage_buffer_unsized : buffer; @group(2) @binding(4) var ro_storage_buffer_sized : buffer<128>; @group(2) @binding(5) var ro_storage_buffer_sized_small : buffer<64>; @group(2) @binding(6) var uniform_buffer_sized : buffer<128>; @group(2) @binding(7) var uniform_buffer_sized_small : buffer<64>; var wg_buffer_sized : buffer<128>; var wg_buffer_sized_small : buffer<64>; var wg_buffer_override_no_default : buffer; var wg_buffer_override_default : buffer; var wg_buffer_override_expr : buffer; `; } const code = ` ${enable} ${kCCommonTypeDecls} @group(0) @binding(0) var t : texture_2d; @group(0) @binding(1) var s : sampler; @group(0) @binding(2) var s_depth : sampler_comparison; @group(0) @binding(3) var t_storage : texture_storage_2d; @group(0) @binding(4) var t_depth : texture_depth_2d; @group(0) @binding(5) var t_multisampled : texture_multisampled_2d; @group(0) @binding(6) var t_external : texture_external; @group(1) @binding(0) var ro_host_shareable : host_shareable; @group(1) @binding(1) var rw_host_shareable : host_shareable; @group(1) @binding(2) var uniform_host_shareable : host_shareable; ${buffer_decls} fn bar(param : ${param.name}) { } var g_u32 : u32; var g_i32 : i32; var g_f32 : f32; var g_bool : bool; var g_vec2 : vec2u; var g_vec3 : vec3i; var g_vec4 : vec4f; var g_mat2x2 : mat2x2f; var g_mat2x3 : mat2x3f; var g_mat2x4 : mat2x4f; var g_mat3x2 : mat3x2f; var g_mat3x3 : mat3x3f; var g_mat3x4 : mat3x4f; var g_mat4x2 : mat4x2f; var g_mat4x3 : mat4x3f; var g_mat4x4 : mat4x4f; var g_array1 : array; var g_array2 : array; var g_array3 : array; var g_array4 : array; var g_array5 : array; var g_constructible : constructible; var g_struct_with_array : struct_with_array; var wg_override_no_default : array; var wg_override_default : array; var wg_override_expr : array; fn foo() { var f_u32 : u32; var f_i32 : i32; var f_f32 : f32; var f_bool : bool; var f_vec2 : vec2u; var f_vec3 : vec3i; var f_vec4 : vec4f; var f_mat2x2 : mat2x2f; var f_mat2x3 : mat2x3f; var f_mat2x4 : mat2x4f; var f_mat3x2 : mat3x2f; var f_mat3x3 : mat3x3f; var f_mat3x4 : mat3x4f; var f_mat4x2 : mat4x2f; var f_mat4x3 : mat4x3f; var f_mat4x4 : mat4x4f; var f_array1 : array; var f_array2 : array; var f_array3 : array; var f_array4 : array; var f_array5 : array; var f_constructible : constructible; var f_struct_with_array : struct_with_array; let ptr_f_u32 = &f_u32; let ptr_f_constructible = &f_constructible; let ptr_g_u32 = &g_u32; let ptr_g_constructible = &g_constructible; let let_let_f_u32 = ptr_f_u32; bar(${arg.value}); } `; const needsUnrestrictedPointerParameters = (kFunctionParamTypeCases[t.params.decl].valid === 'with_unrestricted_pointer_parameters' || arg.needsUnrestrictedPointerParameters) ?? false; const needsBufferView = (kFunctionParamTypeCases[t.params.decl].valid === 'with_buffer_view' || arg.needsBufferView) ?? false; let isValid = parameterMatches(t.params.decl, arg.matches); if (isValid && needsUnrestrictedPointerParameters) { isValid = t.hasLanguageFeature('unrestricted_pointer_parameters'); } if (isValid && needsBufferView) { isValid = t.hasLanguageFeature('unrestricted_pointer_parameters') && t.hasLanguageFeature('buffer_view'); } t.expectCompileResult(isValid, code); }); g.test('no_direct_recursion') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction') .desc(`Test that functions cannot be directly recursive`) .fn(t => { const code = ` fn foo() { foo(); }`; t.expectCompileResult(false, code); }); g.test('no_indirect_recursion') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction') .desc(`Test that functions cannot be indirectly recursive`) .fn(t => { const code = ` fn bar() { foo(); } fn foo() { bar(); }`; t.expectCompileResult(false, code); }); g.test('param_names_must_differ') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-declaration-sec') .desc(`Test that function parameters must have different names`) .params(u => u.combine('p1', ['a', 'b', 'c'] as const).combine('p2', ['a', 'b', 'c'] as const)) .fn(t => { const code = `fn foo(${t.params.p1} : u32, ${t.params.p2} : f32) { }`; t.expectCompileResult(t.params.p1 !== t.params.p2, code); }); const kParamUseCases: Record = { body: `fn foo(param : u32) { let tmp = param; }`, var: `var v : u32 = param; fn foo(param : u32) { }`, const: `const c : u32 = param; fn foo(param : u32) { }`, override: `override o : u32 = param; fn foo(param : u32) { }`, function: `fn bar() { let tmp = param; } fn foo(param : u32) { }`, }; g.test('param_scope_is_function_body') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-declaration-sec') .desc(`Test that function parameters are only in scope in the function body`) .params(u => u.combine('use', keysOf(kParamUseCases))) .fn(t => { t.expectCompileResult(t.params.use === 'body', kParamUseCases[t.params.use]); }); g.test('param_number_matches_call') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-calls') .desc(`Test that function calls have an equal number of arguments as the number of parameters`) .params(u => u .combine('num_args', [0, 1, 2, 3, 4, 255] as const) .combine('num_params', [0, 1, 2, 3, 4, 255] as const) ) .fn(t => { let code = ` fn bar(`; for (let i = 0; i < t.params.num_params; i++) { code += `p${i} : u32,`; } code += `) { }\n`; code += `fn foo() {\nbar(`; for (let i = 0; i < t.params.num_args; i++) { code += `0,`; } code += `);\n}`; t.expectCompileResult(t.params.num_args === t.params.num_params, code); }); const kParamsTypes = ['u32', 'i32', 'f32']; interface ArgValue { value: string; matches: string[]; } const kArgValues: Record = { abstract_int: { value: '0', matches: ['u32', 'i32', 'f32'], }, abstract_float: { value: '0.0', matches: ['f32'], }, unsigned_int: { value: '0u', matches: ['u32'], }, signed_int: { value: '0i', matches: ['i32'], }, float: { value: '0f', matches: ['f32'], }, }; function checkArgTypeMatch(param_type: string, arg_matches: string[]): boolean { for (const match of arg_matches) { if (match === param_type) { return true; } } return false; } g.test('call_arg_types_match_1_param') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-calls') .desc(`Test that the argument types match in order`) .params(u => u .combine('p1_type', kParamsTypes) // .beginSubcases() .combine('arg1_value', keysOf(kArgValues)) ) .fn(t => { const code = ` fn bar(p1 : ${t.params.p1_type}) { } fn foo() { bar(${kArgValues[t.params.arg1_value].value}); }`; const res = checkArgTypeMatch(t.params.p1_type, kArgValues[t.params.arg1_value].matches); t.expectCompileResult(res, code); }); g.test('call_arg_types_match_2_params') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-calls') .desc(`Test that the argument types match in order`) .params(u => u .combine('p1_type', kParamsTypes) .combine('p2_type', kParamsTypes) .beginSubcases() .combine('arg1_value', keysOf(kArgValues)) .combine('arg2_value', keysOf(kArgValues)) ) .fn(t => { const code = ` fn bar(p1 : ${t.params.p1_type}, p2 : ${t.params.p2_type}) { } fn foo() { bar(${kArgValues[t.params.arg1_value].value}, ${kArgValues[t.params.arg2_value].value}); }`; const res = checkArgTypeMatch(t.params.p1_type, kArgValues[t.params.arg1_value].matches) && checkArgTypeMatch(t.params.p2_type, kArgValues[t.params.arg2_value].matches); t.expectCompileResult(res, code); }); g.test('call_arg_types_match_3_params') .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-calls') .desc(`Test that the argument types match in order`) .params(u => u .combine('p1_type', kParamsTypes) .combine('p2_type', kParamsTypes) .combine('p3_type', kParamsTypes) .beginSubcases() .combine('arg1_value', keysOf(kArgValues)) .combine('arg2_value', keysOf(kArgValues)) .combine('arg3_value', keysOf(kArgValues)) ) .fn(t => { const code = ` fn bar(p1 : ${t.params.p1_type}, p2 : ${t.params.p2_type}, p3 : ${t.params.p3_type}) { } fn foo() { bar(${kArgValues[t.params.arg1_value].value}, ${kArgValues[t.params.arg2_value].value}, ${kArgValues[t.params.arg3_value].value}); }`; const res = checkArgTypeMatch(t.params.p1_type, kArgValues[t.params.arg1_value].matches) && checkArgTypeMatch(t.params.p2_type, kArgValues[t.params.arg2_value].matches) && checkArgTypeMatch(t.params.p3_type, kArgValues[t.params.arg3_value].matches); t.expectCompileResult(res, code); }); g.test('param_name_can_shadow_function_name') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests that a function parameter can shadow the function name`) .fn(t => { const code = ` fn foo(foo: i32) -> i32 { return foo; } `; t.expectCompileResult(true, code); }); g.test('param_name_can_shadow_alias') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests that a function parameter can shadow an alias`) .fn(t => { const code = ` alias foo = f32; fn test(foo: i32) -> i32 { return foo; } `; t.expectCompileResult(true, code); }); g.test('param_name_can_shadow_global') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests that a function parameter can shadow a global`) .fn(t => { const code = ` const foo: f32 = 1.2f; fn test(foo: i32) -> i32 { return foo; } `; t.expectCompileResult(true, code); }); g.test('param_comma_placement') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests validation of commas in function parameter lists`) .params(u => u .combine('param_1', [true, false]) .combine('param_2', [true, false]) .combine('comma', [true, false]) ) .fn(t => { const has_p1 = t.params.param_1; const has_p2 = t.params.param_2; const has_c = t.params.comma; const p1 = has_p1 ? 'foo: i32' : ''; const p2 = has_p2 ? 'bar: f32' : ''; const comma = has_c ? ', ' : ' '; const code = ` fn test(${p1}${comma}${p2}) { } `; const success = (!has_p1 && !has_p2 && !has_c) || // no params (has_p1 && !has_p2) || // only p1, which can have a trailing comma or not (!has_p1 && has_p2 && !has_c) || // just p2 (has_p1 && has_p2 && has_c); // both params comma separated t.expectCompileResult(success, code); }); g.test('param_type_can_be_alias') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests that a function parameter type can be an alias`) .fn(t => { const code = ` alias foo = f32; fn test(foo: foo) -> foo { return foo; } `; t.expectCompileResult(true, code); }); g.test('function_name_required') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests the function name is required`) .params(u => u.combine('name', [true, false])) .fn(t => { const has_name = t.params.name; const name = has_name ? 'name' : ''; const code = ` fn ${name}() -> i32 { return 1; } `; t.expectCompileResult(has_name, code); }); g.test('param_type_required') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests the parameter type is required`) .params(u => u.combine('ty', [true, false]).combine('colon', [true, false])) .fn(t => { const has_ty = t.params.ty; const has_colon = t.params.colon; const ty = has_ty ? 'i32' : ''; const colon = has_colon ? ':' : ' '; const code = ` fn f(foo${colon}${ty}) -> i32 { return 1; } `; t.expectCompileResult(has_ty && has_colon, code); }); g.test('body_required') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests the function body is required`) .params(u => u.combine('body', ['braces', 'semi', ''])) .fn(t => { const body = t.params.body === 'braces' ? '{}' : t.params.body === 'semi' ? ';' : ''; const code = ` fn f() ${body} fn other() {} `; t.expectCompileResult(body === '{}', code); }); g.test('parens_required') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests that the parens for a function are required`) .params(u => u.combine('parens', [true, false]).combine('param', [true, false])) .fn(t => { const has_parens = t.params.parens; const has_param = t.params.param; let args = ''; if (has_parens) { args += '('; } if (has_param) { args += 'foo: i32'; } if (has_parens) { args += ')'; } const code = ` fn f ${args} {} `; t.expectCompileResult(has_parens, code); }); g.test('non_module_scoped_function') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests that a non-module-scope function is rejected`) .params(u => u.combine('loc', ['inner', 'outer'])) .fn(t => { const o = `fn a() -> i32 { return 1; }`; let inner = ''; let outer = ''; if (t.params.loc === 'inner') { inner = o; } else { outer = o; } const code = ` ${outer} fn b() { ${inner} } `; t.expectCompileResult(t.params.loc === 'outer', code); }); const kAttributes = { align: { attr: '@align(5)', pass: { func: false, param: false, ret: false, }, }, binding: { attr: '@binding(5)', pass: { func: false, param: false, ret: false, }, }, builtin: { attr: '@builtin(position)', pass: { func: false, param: false, ret: false, }, }, compute: { attr: `@compute`, pass: { func: false, param: false, ret: false, }, }, const: { attr: '@const', pass: { func: false, param: false, ret: false, }, }, diagnostic: { attr: `@diagnostic(off, derivative_uniformity)`, pass: { func: true, param: false, ret: false, }, }, fragment: { attr: `@fragment`, pass: { func: false, param: false, ret: false, }, }, group: { attr: `@group(1)`, pass: { func: false, param: false, ret: false, }, }, id: { attr: `@id(1)`, pass: { func: false, param: false, ret: false, }, }, interpolate: { attr: `@interpolate(linear, center)`, pass: { func: false, param: false, ret: false, }, }, invariant: { attr: `@invariant`, pass: { func: false, param: false, ret: false, }, }, location: { attr: `@location(0)`, pass: { func: false, param: false, ret: false, }, }, must_use: { attr: `@must_use`, pass: { func: true, param: false, ret: false, }, }, size: { attr: `@size(10)`, pass: { func: false, param: false, ret: false, }, }, vertex: { attr: `@vertex`, pass: { func: false, param: false, ret: false, }, }, workgroup_size: { attr: `@workgroup_size(1)`, pass: { func: false, param: false, ret: false, }, }, }; g.test('function_attributes') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests the attributes for a function`) .params(u => u.combine('case', keysOf(kAttributes)).combine('placement', ['func', 'param', 'ret']) ) .fn(t => { const d = kAttributes[t.params.case]; const func = t.params.placement === 'func'; const param = t.params.placement === 'param'; const ret = t.params.placement === 'ret'; const code = ` ${func ? d.attr : ''} fn b(${param ? d.attr : ''} foo: i32) -> ${ret ? d.attr : ''} i32{ return 1; } `; const succeed = t.params.placement === 'func' ? d.pass.func : t.params.placement === 'params' ? d.pass.param : d.pass.ret; t.expectCompileResult(succeed, code); }); g.test('must_use_requires_return') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests the must_use attribute requires a return`) .params(u => u.combine('ret', [true, false])) .fn(t => { let ret = ''; let ret_stmt = ''; if (t.params.ret) { ret = '-> i32'; ret_stmt = 'return 1;'; } const code = ` @must_use fn b() ${ret} { ${ret_stmt} } `; t.expectCompileResult(t.params.ret, code); }); g.test('overload') .specURL('https://www.w3.org/TR/WGSL/#function-declaration-sec') .desc(`Tests that user functions can not overload `) .params(u => u.combine('overload', [true, false])) .fn(t => { let code = 'fn a(f: i32) {}\n'; if (t.params.overload) { code += 'fn a(f: u32) {}'; } t.expectCompileResult(t.params.overload === false, code); });