| export const description = ` |
| Validation tests for array access expressions |
| |
| * Index type |
| * Result type |
| * Early-evaluation errors |
| `; |
| |
| import { makeTestGroup } from '../../../../../common/framework/test_group.js'; |
| import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js'; |
| import { |
| Type, |
| elementTypeOf, |
| kConcreteNumericScalarsAndVectors, |
| kAllBoolScalarsAndVectors, |
| } from '../../../../util/conversion.js'; |
| import { ShaderValidationTest } from '../../shader_validation_test.js'; |
| |
| export const g = makeTestGroup(ShaderValidationTest); |
| |
| g.test('index_type') |
| .desc('Tests valid index types for array access expressions') |
| .params(u => |
| u.combine('type', [ |
| 'bool', |
| 'u32', |
| 'i32', |
| 'abstract-int', |
| 'f32', |
| 'f16', |
| 'abstract-float', |
| 'vec2i', |
| ] as const) |
| ) |
| .fn(t => { |
| const ty = Type[t.params.type]; |
| const enable = ty.requiresF16() ? 'enable f16;' : ''; |
| const code = `${enable} |
| fn foo() { |
| var x = array(1,2,3); |
| let tmp = x[${ty.create(0).wgsl()}]; |
| }`; |
| const expect = |
| t.params.type === 'i32' || t.params.type === 'u32' || t.params.type === 'abstract-int'; |
| t.expectCompileResult(expect, code); |
| }); |
| |
| const kTypes = objectsToRecord([ |
| ...kConcreteNumericScalarsAndVectors, |
| ...kAllBoolScalarsAndVectors, |
| ]); |
| const kTypeKeys = keysOf(kTypes); |
| |
| g.test('result_type') |
| .desc('Tests that correct result type is produced for an access expression') |
| .params(u => |
| u |
| .combine('type', kTypeKeys) |
| .combine('elements', [0, 4] as const) |
| .filter(t => { |
| const ty = kTypes[t.type]; |
| if (t.elements === 0) { |
| if (elementTypeOf(ty) === Type.bool) { |
| return false; |
| } |
| } |
| return true; |
| }) |
| ) |
| .fn(t => { |
| const ty = kTypes[t.params.type]; |
| const enable = ty.requiresF16() ? 'enable f16;' : ''; |
| const arrayTy = Type['array'](t.params.elements, ty); |
| const module_decl = |
| t.params.elements === 0 |
| ? `@group(0) @binding(0) var<storage> x : ${arrayTy.toString()};` |
| : ``; |
| const function_decl = t.params.elements === 0 ? `` : `var x : ${arrayTy.toString()};`; |
| const code = `${enable} |
| ${module_decl} |
| fn foo() { |
| ${function_decl} |
| let tmp1 : ${ty.toString()} = x[0]; |
| let tmp2 : ${ty.toString()} = x[1]; |
| let tmp3 : ${ty.toString()} = x[2]; |
| }`; |
| t.expectCompileResult(true, code); |
| }); |
| |
| interface OutOfBoundsCase { |
| code: string; |
| result: boolean; |
| pipeline?: boolean; |
| value?: number; |
| } |
| |
| const kOutOfBoundsCases: Record<string, OutOfBoundsCase> = { |
| const_module_in_bounds: { |
| code: `const x = array(1,2,3)[0];`, |
| result: true, |
| }, |
| const_module_oob_neg: { |
| code: `const x = array(1,2,3)[-1];`, |
| result: false, |
| }, |
| const_module_oob_pos: { |
| code: `const x = array(1,2,3)[3];`, |
| result: false, |
| }, |
| const_func_in_bounds: { |
| code: `fn foo() { |
| const x = array(1,2,3)[0]; |
| }`, |
| result: true, |
| }, |
| const_func_oob_neg: { |
| code: `fn foo { |
| const x = array(1,2,3)[-1]; |
| }`, |
| result: false, |
| }, |
| const_func_oob_pos: { |
| code: `fn foo { |
| const x = array(1,2,3)[3]; |
| }`, |
| result: false, |
| }, |
| override_in_bounds: { |
| code: `override x : i32; |
| fn y() -> u32 { |
| let tmp = array(1,2,3)[x]; |
| return 0; |
| }`, |
| result: true, |
| pipeline: true, |
| value: 0, |
| }, |
| override_oob_neg: { |
| code: `override x : i32; |
| fn y() -> u32 { |
| let tmp = array(1,2,3)[x]; |
| return 0; |
| }`, |
| result: false, |
| pipeline: true, |
| value: -1, |
| }, |
| override_oob_pos: { |
| code: `override x : i32; |
| fn y() -> u32 { |
| let tmp = array(1,2,3)[x]; |
| return 0; |
| }`, |
| result: false, |
| pipeline: true, |
| value: 3, |
| }, |
| runtime_in_bounds: { |
| code: `fn foo() { |
| let idx = 0; |
| let x = array(1,2,3)[idx]; |
| }`, |
| result: true, |
| }, |
| runtime_oob_neg: { |
| code: `fn foo() { |
| let idx = -1; |
| let x = array(1,2,3)[idx]; |
| }`, |
| result: true, |
| }, |
| runtime_oob_pos: { |
| code: `fn foo() { |
| let idx = 3; |
| let x = array(1,2,3)[idx]; |
| }`, |
| result: true, |
| }, |
| runtime_array_const_oob_neg: { |
| code: `@group(0) @binding(0) var<storage> x : array<u32>; |
| fn y() -> u32 { |
| let tmp = x[-1]; |
| return 0; |
| }`, |
| result: false, |
| }, |
| runtime_array_override_oob_neg: { |
| code: `@group(0) @binding(0) var<storage> v : array<u32>; |
| override x : i32; |
| fn y() -> u32 { |
| let tmp = v[x]; |
| return 0; |
| }`, |
| result: false, |
| pipeline: true, |
| value: -1, |
| }, |
| runtime_nested_array_override_oob_neg: { |
| code: `@group(0) @binding(0) var<storage> v : array<array<u32, 4>>; |
| override x : i32; |
| override w = 0u; |
| fn y() -> u32 { |
| let tmp = v[w][x]; |
| return 0; |
| }`, |
| result: false, |
| pipeline: true, |
| value: -1, |
| }, |
| runtime_nested_array_override_oob_pos: { |
| code: `@group(0) @binding(0) var<storage> v : array<array<u32,4>, 5>; |
| override x : i32; |
| override w = 0u; |
| fn y() -> u32 { |
| let tmp = v[w][x]; |
| return 0; |
| }`, |
| result: false, |
| pipeline: true, |
| value: 4, |
| }, |
| runtime_nested_array_override_pos: { |
| code: `@group(0) @binding(0) var<storage> v : array<array<u32,10>, 2>; |
| override x : i32; |
| override w = 0u; |
| fn y() -> u32 { |
| let tmp = v[w][x]; |
| return 0; |
| }`, |
| result: true, |
| pipeline: true, |
| value: 9, |
| }, |
| runtime_deep_nested_array_override_oob_pos: { |
| code: `@group(0) @binding(0) var<storage> v : array<array<array<u32, 3>, 4>, 5>; |
| override x : i32; |
| override w = 0u; |
| override u = 0u; |
| fn y() -> u32 { |
| let tmp = v[w][u][x]; |
| return 0; |
| }`, |
| result: false, |
| pipeline: true, |
| value: 3, |
| }, |
| runtime_deep_nested_array_override_pos: { |
| code: `@group(0) @binding(0) var<storage> v : array<array<array<u32, 3>, 4>, 5>; |
| override x : i32; |
| override w = 4u; |
| override u = 3u; |
| fn y() -> u32 { |
| let tmp = v[w][u][x]; |
| return 0; |
| }`, |
| result: true, |
| pipeline: true, |
| value: 2, |
| }, |
| runtime_structure_array_override_oob_neg: { |
| code: ` |
| override x : i32; |
| struct S { |
| w : array<u32> |
| } |
| @group(0) @binding(0) var<storage> v : S; |
| fn y() -> u32 { |
| let tmp : u32 = v.w[x]; |
| return 0; |
| }`, |
| result: false, |
| pipeline: true, |
| value: -1, |
| }, |
| runtime_structure_array_override_pos: { |
| code: ` |
| override x : i32; |
| struct S { |
| w : array<u32> |
| } |
| @group(0) @binding(0) var<storage> v : S; |
| fn y() -> u32 { |
| let tmp : u32 = v.w[x]; |
| return 0; |
| }`, |
| result: true, |
| pipeline: true, |
| value: 1, |
| }, |
| runtime_structure_array_override_oob_pos: { |
| code: ` |
| override x : i32; |
| struct S { |
| w : array<u32, 5> |
| } |
| @group(0) @binding(0) var<storage> v : S; |
| fn y() -> u32 { |
| let tmp : u32 = v.w[x]; |
| return 0; |
| }`, |
| result: false, |
| pipeline: true, |
| value: 5, |
| }, |
| runtime_nested_structure_array_override_oob_pos: { |
| code: ` |
| override x : i32; |
| struct S { |
| w : array<u32, 5> |
| } |
| struct S2 { |
| r : S |
| } |
| @group(0) @binding(0) var<storage> v : S2; |
| fn y() -> u32 { |
| let tmp : u32 = v.r.w[x]; |
| return 0; |
| }`, |
| result: false, |
| pipeline: true, |
| value: 5, |
| }, |
| runtime_nested_structure_array_override_pos: { |
| code: ` |
| override x : i32; |
| struct S { |
| w : array<u32, 6> |
| } |
| struct S2 { |
| r : S |
| } |
| @group(0) @binding(0) var<storage> v : S2; |
| fn y() -> u32 { |
| let tmp : u32 = v.r.w[x]; |
| return 0; |
| }`, |
| result: true, |
| pipeline: true, |
| value: 5, |
| }, |
| override_array_cnt_size_zero_unsigned: { |
| code: `override x : u32; |
| var<workgroup> v : array<u32,x>; |
| fn y() -> u32 { |
| return v[0]; |
| }`, |
| result: false, |
| pipeline: true, |
| value: 0, |
| }, |
| override_array_cnt_size_zero_signed: { |
| code: `override x : i32; |
| var<workgroup> v : array<u32,x>; |
| fn y() -> u32 { |
| return v[0]; |
| }`, |
| result: false, |
| pipeline: true, |
| value: 0, |
| }, |
| override_array_cnt_size_neg: { |
| code: `override x : i32; |
| var<workgroup> v : array<u32,x>; |
| fn y() -> u32 { |
| return v[0]; |
| }`, |
| result: false, |
| pipeline: true, |
| value: -1, |
| }, |
| override_array_cnt_size_one: { |
| code: `override x : i32; |
| var<workgroup> v : array<u32,x>; |
| fn y() -> u32 { |
| return v[0]; |
| }`, |
| result: true, |
| pipeline: true, |
| value: 1, |
| }, |
| override_array_dynamic_type_checked_oob_pos: { |
| code: `@group(0) @binding(0) var<storage> v : array<array<array<u32, 3>, 4>, 5>; |
| override x : i32; |
| override w = 0u; |
| fn y() -> u32 { |
| var u = 0; |
| let tmp = v[w][u][x]; |
| return 0; |
| }`, |
| result: false, |
| pipeline: true, |
| value: 3, |
| }, |
| override_array_dynamic_type_checked_oob_neg: { |
| code: `@group(0) @binding(0) var<storage> v : array<array<array<u32, 3>, 4>, 5>; |
| override x : i32; |
| override w = 0u; |
| fn y() -> u32 { |
| var u = 0; |
| let tmp = v[w][u][x]; |
| return 0; |
| }`, |
| result: false, |
| pipeline: true, |
| value: -1, |
| }, |
| override_array_dynamic_type_checked_bounds: { |
| code: `@group(0) @binding(0) var<storage> v : array<array<array<u32, 3>, 4>, 5>; |
| override x : i32; |
| override w = 0u; |
| fn y() -> u32 { |
| var u = 0; |
| let tmp = v[w][u][x]; |
| return 0; |
| }`, |
| result: true, |
| pipeline: true, |
| value: 1, |
| }, |
| }; |
| |
| g.test('early_eval_errors') |
| .desc('Tests early evaluation errors for out-of-bounds indexing') |
| .params(u => u.combine('case', keysOf(kOutOfBoundsCases))) |
| .fn(t => { |
| const testcase = kOutOfBoundsCases[t.params.case]; |
| if (testcase.pipeline) { |
| const v: number = testcase.value ?? 0; |
| t.expectPipelineResult({ |
| expectedResult: testcase.result, |
| code: testcase.code, |
| constants: { x: v }, |
| reference: ['y()'], |
| }); |
| } else { |
| t.expectCompileResult(testcase.result, testcase.code); |
| } |
| }); |
| |
| g.test('abstract_array_concrete_index') |
| .desc('Tests that a concrete index type on an abstract array remains abstract') |
| .fn(t => { |
| const code = ` |
| const idx = 0i; |
| const_assert array(0xfffffffff,2,3)[idx] == 0xfffffffff;`; |
| t.expectCompileResult(true, code); |
| }); |