blob: 92d748c9ec798fd87c970a4a2cb0cd16b4f8397e [file] [log] [blame]
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);
});