blob: 2b561a63e3c14854496aa93cb2576e0df42b5570 [file]
export const description = `Validation tests for @align`;
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);
const kTests = {
blank: {
src: '',
pass: true,
},
one: {
src: '@align(1)',
pass: false,
// EXCEPTION: If the type is valid for some address space, the align value must be a multiple of the type's alignment.
},
four_a: {
src: '@align(4)',
pass: true,
},
four_i: {
src: '@align(4i)',
pass: true,
},
four_u: {
src: '@align(4u)',
pass: true,
},
four_hex: {
src: '@align(0x4)',
pass: true,
},
trailing_comma: {
src: '@align(4,)',
pass: true,
},
const_u: {
src: '@align(u_val)',
pass: true,
},
const_i: {
src: '@align(i_val)',
pass: true,
},
const_expr: {
src: '@align(i_val + 4 - 6)',
pass: false,
// EXCEPTION: If the type is valid for some address space, the align value must be a multiple of the type's alignment.
},
const_expr_2: {
src: '@align(i_val + 8 - 4)',
pass: true,
},
large: {
src: '@align(1073741824)',
pass: true,
},
tabs: {
src: '@\talign\t(4)',
pass: true,
},
comment: {
src: '@/*comment*/align/*comment*/(4)',
pass: true,
},
misspelling: {
src: '@malign(4)',
pass: false,
},
empty: {
src: '@align()',
pass: false,
},
missing_left_paren: {
src: '@align 4)',
pass: false,
},
missing_right_paren: {
src: '@align(4',
pass: false,
},
multiple_values: {
src: '@align(4, 2)',
pass: false,
},
non_power_two: {
src: '@align(3)',
pass: false,
},
const_f: {
src: '@align(f_val)',
pass: false,
},
one_f: {
src: '@align(1.0)',
pass: false,
},
four_f: {
src: '@align(4f)',
pass: false,
},
four_h: {
src: '@align(4h)',
pass: false,
},
no_params: {
src: '@align',
pass: false,
},
zero_a: {
src: '@align(0)',
pass: false,
},
negative: {
src: '@align(-4)',
pass: false,
},
large_no_power_two: {
src: '@align(2147483646)',
pass: false,
},
larger_than_max_i32: {
src: '@align(2147483648)',
pass: false,
},
duplicate: {
src: '@align(4) @align(4)',
pass: false,
},
};
g.test('parsing')
.desc(`Test that @align is parsed correctly.`)
.params(u => u.combine('align', keysOf(kTests)))
.fn(t => {
const src = kTests[t.params.align].src;
const code = `
const i_val: i32 = 4;
const u_val: u32 = 4;
const f_val: f32 = 4.2;
struct B {
${src} a: i32,
}
`;
t.expectCompileResult(kTests[t.params.align].pass, code);
});
g.test('required_alignment')
.desc('Test that the align with an invalid size is an error')
.params(u =>
u
.combine('decl', ['var', 'const', 'let'] as const)
.combine('address_space', [
'storage',
'uniform',
'workgroup',
'function',
'private',
'immediate',
] as const)
.combine('align', [1, 2, 'alignment', 32])
.beginSubcases()
.combine('type', [
// Storage is used for all non-uniform address spaces.
{ name: 'i32', storage: 4, uniform: 4 },
{ name: 'u32', storage: 4, uniform: 4 },
{ name: 'f32', storage: 4, uniform: 4 },
{ name: 'f16', storage: 2, uniform: 2 },
{ name: 'atomic<i32>', storage: 4, uniform: 4 },
{ name: 'vec2<i32>', storage: 8, uniform: 8 },
{ name: 'vec2<f16>', storage: 4, uniform: 4 },
{ name: 'vec3<u32>', storage: 16, uniform: 16 },
{ name: 'vec3<f16>', storage: 8, uniform: 8 },
{ name: 'vec4<f32>', storage: 16, uniform: 16 },
{ name: 'vec4<f16>', storage: 8, uniform: 8 },
{ name: 'mat2x2<f32>', storage: 8, uniform: 8 },
{ name: 'mat3x2<f32>', storage: 8, uniform: 8 },
{ name: 'mat4x2<f32>', storage: 8, uniform: 8 },
{ name: 'mat2x2<f16>', storage: 4, uniform: 4 },
{ name: 'mat3x2<f16>', storage: 4, uniform: 4 },
{ name: 'mat4x2<f16>', storage: 4, uniform: 4 },
{ name: 'mat2x3<f32>', storage: 16, uniform: 16 },
{ name: 'mat3x3<f32>', storage: 16, uniform: 16 },
{ name: 'mat4x3<f32>', storage: 16, uniform: 16 },
{ name: 'mat2x3<f16>', storage: 8, uniform: 8 },
{ name: 'mat3x3<f16>', storage: 8, uniform: 8 },
{ name: 'mat4x3<f16>', storage: 8, uniform: 8 },
{ name: 'mat2x4<f32>', storage: 16, uniform: 16 },
{ name: 'mat3x4<f32>', storage: 16, uniform: 16 },
{ name: 'mat4x4<f32>', storage: 16, uniform: 16 },
{ name: 'mat2x4<f16>', storage: 8, uniform: 8 },
{ name: 'mat3x4<f16>', storage: 8, uniform: 8 },
{ name: 'mat4x4<f16>', storage: 8, uniform: 8 },
{ name: 'array<vec2<i32>, 2>', storage: 8, uniform: 16 },
{ name: 'array<vec4<i32>, 2>', storage: 16, uniform: 16 },
{ name: 'S', storage: 8, uniform: 16 },
{ name: 'array<u32>', storage: 4, uniform: 4 },
])
.filter(t => {
if (t.decl === 'let' && t.address_space !== 'function') {
return false;
}
if (
t.decl === 'const' &&
!(t.address_space === 'private' || t.address_space === 'function')
) {
// Private is used as placeholder for module-scope const and function as placeholder for function-scope const.
return false;
}
if (t.type.name.startsWith('atomic')) {
if (t.address_space !== 'storage' && t.address_space !== 'workgroup') {
return false;
}
if (t.decl !== 'var') {
return false;
}
}
if (t.type.name === 'array<u32>' && t.address_space !== 'storage') {
return false;
}
return true;
})
)
.fn(t => {
t.skipIf(
t.params.address_space === 'immediate' && !t.hasLanguageFeature('immediate_address_space'),
'Immediate address space not supported'
);
// If the `uniform_buffer_standard_layout` feature is supported, the `uniform` address space has
// the same layout constraints as `storage`.
const has_ubo_std_layout = t.hasLanguageFeature('uniform_buffer_standard_layout');
let code = '';
if (t.params.type.name.includes('f16')) {
code += 'enable f16;\n';
}
// Testing the struct case, generate the struct
if (t.params.type.name === 'S') {
code += `struct S {
a: mat4x2<f32>, // Align 8
b: array<vec${
t.params.address_space !== 'uniform' || has_ubo_std_layout ? 2 : 4
}<i32>, 2>, // Storage align 8, uniform 16
}
`;
}
// Alignment value listed in the spec
const min_align =
t.params.address_space !== 'uniform' || has_ubo_std_layout
? `${t.params.type.storage}`
: `${t.params.type.uniform}`;
const align = t.params.align === 'alignment' ? min_align : t.params.align;
let address_space: string = t.params.address_space;
if (t.params.address_space === 'storage') {
// atomics require read_write, not just the default of read
address_space = 'storage, read_write';
}
let decl: string = t.params.decl;
if (decl === 'var') {
decl = `var<${address_space}>`;
}
const init = t.params.decl === 'let' || t.params.decl === 'const' ? ' = MyStruct()' : '';
const module_decl =
t.params.address_space === 'function'
? ''
: `${
t.params.decl === 'var' &&
(t.params.address_space === 'uniform' || t.params.address_space === 'storage')
? '@group(0) @binding(0)'
: ''
}
${decl} a : MyStruct${init};`;
const func_decl = t.params.address_space === 'function' ? `${decl} a : MyStruct${init};` : '';
code += `struct MyStruct {
@align(${align}) a: ${t.params.type.name},
}
${module_decl}`;
code += `
fn foo() {
${func_decl}
}`;
let fails = align < min_align;
if (!has_ubo_std_layout) {
// An array of `vec2` in uniform will not validate because, while the alignment on the array
// itself is fine, the `vec2` element inside the array will have the wrong alignment. Uniform
// requires that inner vec2 to have an align 16 which can only be done by specifying `vec4`
// instead.
fails ||= t.params.address_space === 'uniform' && t.params.type.name.startsWith('array<vec2');
}
t.expectCompileResult(!fails, code);
});
g.test('placement')
.desc('Tests the locations @align is allowed to appear')
.params(u =>
u
.combine('scope', [
'private-var',
'storage-var',
'struct-member',
'fn-decl',
'fn-param',
'fn-var',
'fn-return',
'while-stmt',
undefined,
] as const)
.combine('attribute', [
{
'private-var': false,
'storage-var': false,
'struct-member': true,
'fn-decl': false,
'fn-param': false,
'fn-var': false,
'fn-return': false,
'while-stmt': false,
},
])
.beginSubcases()
)
.fn(t => {
const scope = t.params.scope;
const attr = '@align(32)';
const code = `
${scope === 'private-var' ? attr : ''}
var<private> priv_var : i32;
${scope === 'storage-var' ? attr : ''}
@group(0) @binding(0)
var<storage> stor_var : i32;
struct A {
${scope === 'struct-member' ? attr : ''}
a : i32,
}
@vertex
${scope === 'fn-decl' ? attr : ''}
fn f(
${scope === 'fn-param' ? attr : ''}
@location(0) b : i32,
) -> ${scope === 'fn-return' ? attr : ''} @builtin(position) vec4f {
${scope === 'fn-var' ? attr : ''}
var<function> func_v : i32;
${scope === 'while-stmt' ? attr : ''}
while false {}
return vec4(1, 1, 1, 1);
}
`;
t.expectCompileResult(scope === undefined || t.params.attribute[scope], code);
});
g.test('multi_align')
.desc('Tests that align multiple times is an error')
.params(u => u.combine('multi', [true, false]))
.fn(t => {
let code = `struct A {
@align(128) `;
if (t.params.multi === true) {
code += '@align(128) ';
}
code += `a : i32,
}
@fragment
fn main() -> @location(0) vec4<f32> {
return vec4(1., 1., 1., 1.);
}`;
t.expectCompileResult(!t.params.multi, code);
});