| 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<u32> |
| } |
| |
| 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<constructible, 4> |
| } |
| |
| override override_no_default : u32; |
| override override_default = 4u; |
| override override_expr = override_default + 2; |
| |
| `; |
| |
| const kVertexPosCases: Record<string, VertexPosCase> = { |
| 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<string, RetTypeCase> = { |
| // 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<u32, 4>`, value: ``, valid: true }, |
| array2: { name: `array<vec2f, 2>`, value: ``, valid: true }, |
| array3: { name: `array<constructible, 4>`, value: ``, valid: true }, |
| array4: { name: `array<mat2x2f, 4>`, value: ``, valid: true }, |
| array5: { name: `array<bool, 4>`, value: ``, valid: true }, |
| struct1: { name: `constructible`, value: ``, valid: true }, |
| struct2: { name: `struct_with_array`, value: ``, valid: true }, |
| |
| // Non-constructible types. |
| runtime_array: { name: `array<u32>`, value: ``, valid: false }, |
| runtime_struct: { name: `runtime_array_struct`, value: ``, valid: false }, |
| override_array: { name: `array<u32, override_size>`, value: ``, valid: false }, |
| atomic_u32: { name: `atomic<u32>`, value: `atomic_wg`, valid: false }, |
| atomic_struct: { name: `atomic_struct`, value: ``, valid: false }, |
| texture_sample: { name: `texture_2d<f32>`, value: `t`, valid: false }, |
| texture_depth: { name: `texture_depth_2d`, value: `t_depth`, valid: false }, |
| texture_multisampled: { |
| name: `texture_multisampled_2d<f32>`, |
| value: `t_multisampled`, |
| valid: false, |
| }, |
| texture_storage: { |
| name: `texture_storage_2d<rgba8unorm, write>`, |
| 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<workgroup, atomic<u32>>`, 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<u32> |
| }; |
| |
| override override_size : u32; |
| |
| var<workgroup> atomic_wg : atomic<u32>; |
| |
| @group(0) @binding(0) |
| var t : texture_2d<f32>; |
| @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<rgba8unorm, write>; |
| @group(0) @binding(4) |
| var t_depth : texture_depth_2d; |
| @group(0) @binding(5) |
| var t_multisampled : texture_multisampled_2d<f32>; |
| @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<string, ParamTypeCase> = { |
| // 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<u32, 4>`, valid: true }, |
| array2: { name: `array<vec2f, 2>`, valid: true }, |
| array3: { name: `array<constructible, 4>`, valid: true }, |
| array4: { name: `array<mat2x2f, 4>`, valid: true }, |
| array5: { name: `array<bool, 4>`, valid: true }, |
| struct1: { name: `constructible`, valid: true }, |
| struct2: { name: `struct_with_array`, valid: true }, |
| |
| // Non-constructible types. |
| runtime_array: { name: `array<u32>`, valid: false }, |
| runtime_struct: { name: `runtime_array_struct`, valid: false }, |
| override_array: { name: `array<u32, override_size>`, valid: false }, |
| atomic_u32: { name: `atomic<u32>`, valid: false }, |
| atomic_struct: { name: `atomic_struct`, valid: false }, |
| |
| // Textures and samplers. |
| texture_sample: { name: `texture_2d<f32>`, valid: true }, |
| texture_depth: { name: `texture_depth_2d`, valid: true }, |
| texture_multisampled: { |
| name: `texture_multisampled_2d<f32>`, |
| valid: true, |
| }, |
| texture_storage: { name: `texture_storage_2d<rgba8unorm, write>`, valid: true }, |
| sampler: { name: `sampler`, valid: true }, |
| sampler_comparison: { name: `sampler_comparison`, valid: true }, |
| |
| // Valid pointers. |
| ptr1: { name: `ptr<function, u32>`, valid: true }, |
| ptr2: { name: `ptr<function, constructible>`, valid: true }, |
| ptr3: { name: `ptr<private, u32>`, valid: true }, |
| ptr4: { name: `ptr<private, constructible>`, valid: true }, |
| |
| // Pointers only valid with unrestricted_pointer_parameters |
| ptr5: { name: `ptr<storage, u32>`, valid: 'with_unrestricted_pointer_parameters' }, |
| ptr6: { name: `ptr<storage, u32, read>`, valid: 'with_unrestricted_pointer_parameters' }, |
| ptr7: { name: `ptr<storage, u32, read_write>`, valid: 'with_unrestricted_pointer_parameters' }, |
| ptr8: { name: `ptr<uniform, u32>`, valid: 'with_unrestricted_pointer_parameters' }, |
| ptr9: { name: `ptr<workgroup, u32>`, valid: 'with_unrestricted_pointer_parameters' }, |
| ptr10: { |
| name: `ptr<storage, host_shareable, read_write>`, |
| valid: 'with_unrestricted_pointer_parameters', |
| }, |
| ptr11: { |
| name: `ptr<storage, host_shareable, read>`, |
| valid: 'with_unrestricted_pointer_parameters', |
| }, |
| ptr12: { |
| name: `ptr<uniform, host_shareable>`, |
| valid: 'with_unrestricted_pointer_parameters', |
| }, |
| ptrWorkgroupAtomic: { |
| name: `ptr<workgroup, atomic<u32>>`, |
| valid: 'with_unrestricted_pointer_parameters', |
| }, |
| ptrWorkgroupNestedAtomic: { |
| name: `ptr<workgroup, array<atomic<u32>,1>>`, |
| valid: 'with_unrestricted_pointer_parameters', |
| }, |
| ptrWorkgroupOverrideNoDefault: { |
| name: `ptr<workgroup, array<u32, override_no_default>>`, |
| valid: 'with_unrestricted_pointer_parameters', |
| }, |
| ptrWorkgroupOverrideWithDefault: { |
| name: `ptr<workgroup, array<f32, override_default>>`, |
| valid: 'with_unrestricted_pointer_parameters', |
| }, |
| ptrWorkgroupOverrideExpr: { |
| name: `ptr<workgroup, array<vec4f, override_expr>>`, |
| valid: 'with_unrestricted_pointer_parameters', |
| }, |
| |
| // Invalid pointers. |
| invalid_ptr1: { name: `ptr<handle, u32>`, valid: false }, // Can't spell handle address space |
| invalid_ptr2: { name: `ptr<not_an_address_space, u32>`, valid: false }, |
| invalid_ptr3: { name: `ptr<storage>`, valid: false }, // No store type |
| invalid_ptr4: { name: `ptr<private,u32,read>`, valid: false }, // Can't specify access mode |
| invalid_ptr5: { name: `ptr<private,u32,write>`, valid: false }, // Can't specify access mode |
| invalid_ptr6: { name: `ptr<private,u32,read_write>`, valid: false }, // Can't specify access mode |
| invalid_ptr7: { name: `ptr<private,clamp>`, valid: false }, // Invalid store type |
| invalid_ptr8: { name: `ptr<function, texture_external>`, valid: false }, // non-constructible pointer type |
| |
| // Buffers (need buffer_view and unrestricted_pointer_parameters) |
| ptrBufferUnsized_storage: { |
| name: `ptr<storage, buffer, read_write>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferSized_storage: { |
| name: `ptr<storage, buffer<128>, read_write>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferSizedSmall_storage: { |
| name: `ptr<storage, buffer<64>, read_write>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferUnsized_ro_storage: { |
| name: `ptr<storage, buffer>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferSized_ro_storage: { |
| name: `ptr<storage, buffer<128>>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferSizedSmall_ro_storage: { |
| name: `ptr<storage, buffer<64>>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferUnsized_uniform: { |
| name: `ptr<uniform, buffer>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferSized_uniform: { |
| name: `ptr<uniform, buffer<128>>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferSizedSmall_uniform: { |
| name: `ptr<uniform, buffer<64>>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferUnsized_workgroup: { |
| name: `ptr<workgroup, buffer>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferSized_workgroup: { |
| name: `ptr<workgroup, buffer<128>>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferSizedSmall_workgroup: { |
| name: `ptr<workgroup, buffer<64>>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferOverrideNoDefault_workgroup: { |
| name: `ptr<workgroup, buffer<override_no_default>>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferOverrideDefault_workgroup: { |
| name: `ptr<workgroup, buffer<override_default>>`, |
| valid: 'with_buffer_view', |
| }, |
| ptrBufferOverrideExpr_workgroup: { |
| name: `ptr<workgroup, buffer<override_expr>>`, |
| 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<string, ParamValueCase> = { |
| // 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<bool>()`, matches: [] }, |
| vec3u_constructor: { value: `vec3u()`, matches: [] }, |
| vec3i_constructor: { value: `vec3i()`, matches: ['vec3'] }, |
| vec3f_constructor: { value: `vec3f()`, matches: [] }, |
| vec3b_constructor: { value: `vec3<bool>()`, matches: [] }, |
| vec4u_constructor: { value: `vec4u()`, matches: [] }, |
| vec4i_constructor: { value: `vec4i()`, matches: [] }, |
| vec4f_constructor: { value: `vec4f()`, matches: ['vec4'] }, |
| vec4b_constructor: { value: `vec4<bool>()`, 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<u32, 4>()`, matches: ['array1'] }, |
| array2_constructor: { value: `array<vec2f, 2>()`, matches: ['array2'] }, |
| array3_constructor: { value: `array<constructible, 4>()`, matches: ['array3'] }, |
| array4_constructor: { value: `array<mat2x2f, 4>()`, matches: ['array4'] }, |
| array5_constructor: { value: `array<bool, 4>()`, 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, read_write> storage_buffer_unsized : buffer; |
| @group(2) @binding(1) var<storage, read_write> storage_buffer_sized : buffer<128>; |
| @group(2) @binding(2) var<storage, read_write> storage_buffer_sized_small : buffer<64>; |
| @group(2) @binding(3) var<storage> ro_storage_buffer_unsized : buffer; |
| @group(2) @binding(4) var<storage> ro_storage_buffer_sized : buffer<128>; |
| @group(2) @binding(5) var<storage> ro_storage_buffer_sized_small : buffer<64>; |
| @group(2) @binding(6) var<uniform> uniform_buffer_sized : buffer<128>; |
| @group(2) @binding(7) var<uniform> uniform_buffer_sized_small : buffer<64>; |
| |
| var<workgroup> wg_buffer_sized : buffer<128>; |
| var<workgroup> wg_buffer_sized_small : buffer<64>; |
| var<workgroup> wg_buffer_override_no_default : buffer<override_no_default>; |
| var<workgroup> wg_buffer_override_default : buffer<override_default>; |
| var<workgroup> wg_buffer_override_expr : buffer<override_expr>; |
| `; |
| } |
| |
| const code = ` |
| ${enable} |
| |
| ${kCCommonTypeDecls} |
| @group(0) @binding(0) |
| var t : texture_2d<f32>; |
| @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<rgba8unorm, write>; |
| @group(0) @binding(4) |
| var t_depth : texture_depth_2d; |
| @group(0) @binding(5) |
| var t_multisampled : texture_multisampled_2d<f32>; |
| @group(0) @binding(6) |
| var t_external : texture_external; |
| |
| @group(1) @binding(0) |
| var<storage> ro_host_shareable : host_shareable; |
| @group(1) @binding(1) |
| var<storage, read_write> rw_host_shareable : host_shareable; |
| @group(1) @binding(2) |
| var<uniform> uniform_host_shareable : host_shareable; |
| |
| ${buffer_decls} |
| |
| fn bar(param : ${param.name}) { } |
| |
| var<private> g_u32 : u32; |
| var<private> g_i32 : i32; |
| var<private> g_f32 : f32; |
| var<private> g_bool : bool; |
| var<private> g_vec2 : vec2u; |
| var<private> g_vec3 : vec3i; |
| var<private> g_vec4 : vec4f; |
| var<private> g_mat2x2 : mat2x2f; |
| var<private> g_mat2x3 : mat2x3f; |
| var<private> g_mat2x4 : mat2x4f; |
| var<private> g_mat3x2 : mat3x2f; |
| var<private> g_mat3x3 : mat3x3f; |
| var<private> g_mat3x4 : mat3x4f; |
| var<private> g_mat4x2 : mat4x2f; |
| var<private> g_mat4x3 : mat4x3f; |
| var<private> g_mat4x4 : mat4x4f; |
| var<private> g_array1 : array<u32, 4>; |
| var<private> g_array2 : array<vec2f, 2>; |
| var<private> g_array3 : array<constructible, 4>; |
| var<private> g_array4 : array<mat2x2f, 4>; |
| var<private> g_array5 : array<bool, 4>; |
| var<private> g_constructible : constructible; |
| var<private> g_struct_with_array : struct_with_array; |
| |
| var<workgroup> wg_override_no_default : array<u32, override_no_default>; |
| var<workgroup> wg_override_default : array<f32, override_default>; |
| var<workgroup> wg_override_expr : array<vec4f, override_expr>; |
| |
| 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<u32, 4>; |
| var f_array2 : array<vec2f, 2>; |
| var f_array3 : array<constructible, 4>; |
| var f_array4 : array<mat2x2f, 4>; |
| var f_array5 : array<bool, 4>; |
| 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<string, string> = { |
| body: `fn foo(param : u32) { |
| let tmp = param; |
| }`, |
| var: `var<private> 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<string, ArgValue> = { |
| 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); |
| }); |