blob: 3c1ec06d4bd5ebd9ebdea9c194cdf6bc8c2f8b4e [file] [log] [blame]
export const description = `
Validation tests for let declarations
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { keysOf } from '../../../../common/util/data_tables.js';
import { ShaderValidationTest } from '../shader_validation_test.js';
export const g = makeTestGroup(ShaderValidationTest);
interface Case {
code: string;
valid: boolean | 'texture_and_sampler_let';
decls?: string;
}
const kTypeCases: Record<string, Case> = {
bool: {
code: `let x : bool = true;`,
valid: true,
},
i32: {
code: `let x : i32 = 1i;`,
valid: true,
},
u32: {
code: `let x : u32 = 1u;`,
valid: true,
},
f32: {
code: `let x : f32 = 1f;`,
valid: true,
},
f16: {
code: `let x : f16 = 1h;`,
valid: true,
},
vec2i: {
code: `let x : vec2i = vec2i();`,
valid: true,
},
vec3u: {
code: `let x : vec3u = vec3u();`,
valid: true,
},
vec4f: {
code: `let x : vec4f = vec4f();`,
valid: true,
},
mat2x2: {
code: `let x : mat2x2f = mat2x2f();`,
valid: true,
},
mat4x3f: {
code: `let x : mat4x3<f32> = mat4x3<f32>();`,
valid: true,
},
array_sized: {
code: `let x : array<u32, 4> = array(1,2,3,4);`,
valid: true,
},
array_runtime: {
code: `let x : array<u32> = array(1,2,3);`,
valid: false,
},
struct: {
code: `let x : S = S(0);`,
valid: true,
decls: `struct S { x : u32 }`,
},
atomic: {
code: `let x : atomic<u32> = 0;`,
valid: false,
},
ptr_function: {
code: `
var x : i32;
let y : ptr<function, i32> = &x;`,
valid: true,
},
ptr_storage: {
code: `let y : ptr<storage, i32> = &x[0];`,
valid: true,
decls: `@group(0) @binding(0) var<storage> x : array<i32, 4>;`,
},
load_rule: {
code: `
var x : i32 = 1;
let y : i32 = x;`,
valid: true,
},
texture_2d: {
code: `let x = tex2d;`,
valid: 'texture_and_sampler_let',
decls: `@group(0) @binding(0) var tex2d : texture_2d<f32>;`,
},
texture_storage_1d: {
code: `let x : texture_storage_1d<rgba32float, write> = tex1d;`,
valid: 'texture_and_sampler_let',
decls: `@group(0) @binding(0) var tex1d : texture_storage_1d<rgba32float, write>;`,
},
sampler: {
code: `let s = samp;`,
valid: 'texture_and_sampler_let',
decls: `@group(0) @binding(0) var samp : sampler;`,
},
sampler_comparison: {
code: `let s : sampler_comparison = samp_comp;`,
valid: 'texture_and_sampler_let',
decls: `@group(0) @binding(0) var samp_comp : sampler_comparison;`,
},
};
g.test('type')
.desc('Test let types')
.params(u => u.combine('case', keysOf(kTypeCases)))
.fn(t => {
if (t.params.case === 'f16') {
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
}
const testcase = kTypeCases[t.params.case];
const code = `
${t.params.case === 'f16' ? 'enable f16;' : ''}
${testcase.decls ?? ''}
fn foo() {
${testcase.code}
}`;
let expect: boolean = testcase.valid === true;
if (testcase.valid === 'texture_and_sampler_let') {
expect = t.hasLanguageFeature('texture_and_sampler_let');
}
t.expectCompileResult(expect, code);
});
const kInitCases: Record<string, Case> = {
no_init: {
code: `let x : u32;`,
valid: false,
},
no_type: {
code: `let x = 1;`,
valid: true,
},
init_matching_type: {
code: `let x : u32 = 1u;`,
valid: true,
},
init_mismatch_type: {
code: `let x : u32 = 1i;`,
valid: false,
},
ptr_type_mismatch: {
code: `var x : i32;\nlet y : ptr<function, u32> = &x;`,
valid: false,
},
ptr_access_mismatch: {
code: `let y : ptr<storage, u32, read> = &x;`,
valid: false,
decls: `@group(0) @binding(0) var<storage, read_write> x : u32;`,
},
ptr_addrspace_mismatch: {
code: `let y = ptr<storage, u32> = &x;`,
valid: false,
decls: `@group(0) @binding(0) var<uniform> x : u32;`,
},
init_const_expr: {
code: `let y = x * 2;`,
valid: true,
decls: `const x = 1;`,
},
init_override_expr: {
code: `let y = x + 1;`,
valid: true,
decls: `override x = 1;`,
},
init_runtime_expr: {
code: `var x = 1;\nlet y = x << 1;`,
valid: true,
},
};
g.test('initializer')
.desc('Test let initializers')
.params(u => u.combine('case', keysOf(kInitCases)))
.fn(t => {
const testcase = kInitCases[t.params.case];
const code = `
${testcase.decls ?? ''}
fn foo() {
${testcase.code}
}`;
const expect = testcase.valid;
t.expectCompileResult(expect === true, code);
});
g.test('module_scope')
.desc('Test that let declarations are disallowed module scope')
.fn(t => {
const code = `let x = 0;`;
t.expectCompileResult(false, code);
});
const kTestTypes = [
'f32',
'i32',
'u32',
'bool',
'vec2<f32>',
'vec2<i32>',
'vec2<u32>',
'vec2<bool>',
'vec3<f32>',
'vec3<i32>',
'vec3<u32>',
'vec3<bool>',
'vec4<f32>',
'vec4<i32>',
'vec4<u32>',
'vec4<bool>',
'mat2x2<f32>',
'mat2x3<f32>',
'mat2x4<f32>',
'mat3x2<f32>',
'mat3x3<f32>',
'mat3x4<f32>',
'mat4x2<f32>',
'mat4x3<f32>',
'mat4x4<f32>',
// [1]: 12 is a random number here. find a solution to replace it.
'array<f32, 12>',
'array<i32, 12>',
'array<u32, 12>',
'array<bool, 12>',
] as const;
g.test('initializer_type')
.desc(
`
If present, the initializer's type must match the store type of the variable.
Testing scalars, vectors, and matrices of every dimension and type.
TODO: add test for: structs - arrays of vectors and matrices - arrays of different length
`
)
.params(u => u.beginSubcases().combine('lhsType', kTestTypes).combine('rhsType', kTestTypes))
.fn(t => {
const { lhsType, rhsType } = t.params;
const code = `
@fragment
fn main() {
let a : ${lhsType} = ${rhsType}();
}
`;
const expectation = lhsType === rhsType;
t.expectCompileResult(expectation, code);
});