blob: b428e4ee95843d48f07c9d60fa99c281ebc1be42 [file] [log] [blame]
export const description = `Validation tests for function alias analysis`;
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 Use {
is_write: boolean;
gen: (ref: string) => string;
}
const kUses: Record<string, Use> = {
no_access: { is_write: false, gen: ref => `{ let p = &*&${ref}; }` },
assign: { is_write: true, gen: ref => `${ref} = 42;` },
compound_assign_lhs: { is_write: true, gen: ref => `${ref} += 1;` },
compound_assign_rhs: { is_write: false, gen: ref => `{ var tmp : i32; tmp += ${ref}; }` },
increment: { is_write: true, gen: ref => `${ref}++;` },
binary_lhs: { is_write: false, gen: ref => `_ = ${ref} + 1;` },
binary_rhs: { is_write: false, gen: ref => `_ = 1 + ${ref};` },
unary_minus: { is_write: false, gen: ref => `_ = -${ref};` },
bitcast: { is_write: false, gen: ref => `_ = bitcast<f32>(${ref});` },
convert: { is_write: false, gen: ref => `_ = f32(${ref});` },
builtin_arg: { is_write: false, gen: ref => `_ = abs(${ref});` },
index_access: { is_write: false, gen: ref => `{ var arr : array<i32, 4>; _ = arr[${ref}]; }` },
let_init: { is_write: false, gen: ref => `{ let tmp = ${ref}; }` },
var_init: { is_write: false, gen: ref => `{ var tmp = ${ref}; }` },
return: { is_write: false, gen: ref => `{ return ${ref}; }` },
switch_cond: { is_write: false, gen: ref => `switch(${ref}) { default { break; } }` },
};
type UseName = keyof typeof kUses;
function shouldPass(aliased: boolean, ...uses: UseName[]): boolean {
// Expect fail if the pointers are aliased and at least one of the accesses is a write.
// If either of the accesses is a "no access" then expect pass.
return !aliased || !uses.some(u => kUses[u].is_write) || uses.includes('no_access');
}
type AddressSpace = 'private' | 'function' | 'storage' | 'uniform' | 'workgroup';
const kWritableAddressSpaces = ['private', 'function', 'storage', 'workgroup'] as const;
function ptr(addressSpace: AddressSpace, type: string) {
switch (addressSpace) {
case 'function':
return `ptr<function, ${type}>`;
case 'private':
return `ptr<private, ${type}>`;
case 'storage':
return `ptr<storage, ${type}, read_write>`;
case 'uniform':
return `ptr<uniform, ${type}>`;
case 'workgroup':
return `ptr<workgroup, ${type}>`;
}
}
function declareModuleScopeVar(
name: string,
addressSpace: 'private' | 'storage' | 'uniform' | 'workgroup',
type: string
) {
const binding = name === 'x' ? 0 : 1;
switch (addressSpace) {
case 'private':
return `var<private> ${name} : ${type};`;
case 'storage':
return `@binding(${binding}) @group(0) var<storage, read_write> ${name} : ${type};`;
case 'uniform':
return `@binding(${binding}) @group(0) var<uniform> ${name} : ${type};`;
case 'workgroup':
return `var<workgroup> ${name} : ${type};`;
}
}
function maybeDeclareModuleScopeVar(name: string, addressSpace: AddressSpace, type: string) {
if (addressSpace === 'function') {
return '';
}
return declareModuleScopeVar(name, addressSpace, type);
}
function maybeDeclareFunctionScopeVar(name: string, addressSpace: AddressSpace, type: string) {
switch (addressSpace) {
case 'function':
return `var ${name} : ${type};`;
default:
return ``;
}
}
/**
* @returns true if a pointer of the given address space requires the
* 'unrestricted_pointer_parameters' language feature.
*/
function requiresUnrestrictedPointerParameters(addressSpace: AddressSpace) {
return addressSpace !== 'function' && addressSpace !== 'private';
}
g.test('two_pointers')
.desc(`Test aliasing of two pointers passed to a function.`)
.params(u =>
u
.combine('address_space', kWritableAddressSpaces)
.combine('aliased', [true, false])
.beginSubcases()
.combine('a_use', keysOf(kUses))
.combine('b_use', keysOf(kUses))
)
.fn(t => {
if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
}
const code = `
${maybeDeclareModuleScopeVar('x', t.params.address_space, 'i32')}
${maybeDeclareModuleScopeVar('y', t.params.address_space, 'i32')}
fn callee(pa : ${ptr(t.params.address_space, 'i32')},
pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
${kUses[t.params.a_use].gen(`*pa`)}
${kUses[t.params.b_use].gen(`*pb`)}
return 0;
}
fn caller() {
${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'i32')}
${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'i32')}
callee(&x, ${t.params.aliased ? `&x` : `&y`});
}
`;
t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
});
g.test('two_pointers_to_array_elements')
.desc(`Test aliasing of two array element pointers passed to a function.`)
.params(u =>
u
.combine('address_space', kWritableAddressSpaces)
.combine('index', [0, 1])
.combine('aliased', [true, false])
.beginSubcases()
.combine('a_use', keysOf(kUses))
.combine('b_use', keysOf(kUses))
)
.fn(t => {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
const code = `
${maybeDeclareModuleScopeVar('x', t.params.address_space, 'array<i32, 4>')}
${maybeDeclareModuleScopeVar('y', t.params.address_space, 'array<i32, 4>')}
fn callee(pa : ${ptr(t.params.address_space, 'i32')},
pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
${kUses[t.params.a_use].gen(`*pa`)}
${kUses[t.params.b_use].gen(`*pb`)}
return 0;
}
fn caller() {
${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'array<i32, 4>')}
${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'array<i32, 4>')}
callee(&x[${t.params.index}], ${t.params.aliased ? `&x[0]` : `&y[0]`});
}
`;
t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
});
g.test('two_pointers_to_array_elements_indirect')
.desc(
`Test aliasing of two array pointers passed to a function, which indexes those arrays and then
passes the element pointers to another function.`
)
.params(u =>
u
.combine('address_space', kWritableAddressSpaces)
.combine('index', [0, 1])
.combine('aliased', [true, false])
.beginSubcases()
.combine('a_use', keysOf(kUses))
.combine('b_use', keysOf(kUses))
)
.fn(t => {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
const code = `
${maybeDeclareModuleScopeVar('x', t.params.address_space, 'array<i32, 4>')}
${maybeDeclareModuleScopeVar('y', t.params.address_space, 'array<i32, 4>')}
fn callee(pa : ${ptr(t.params.address_space, 'i32')},
pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
${kUses[t.params.a_use].gen(`*pa`)}
${kUses[t.params.b_use].gen(`*pb`)}
return 0;
}
fn index(pa : ${ptr(t.params.address_space, 'array<i32, 4>')},
pb : ${ptr(t.params.address_space, 'array<i32, 4>')}) -> i32 {
return callee(&(*pa)[${t.params.index}], &(*pb)[0]);
}
fn caller() {
${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'array<i32, 4>')}
${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'array<i32, 4>')}
index(&x, ${t.params.aliased ? `&x` : `&y`});
}
`;
t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
});
g.test('two_pointers_to_struct_members')
.desc(`Test aliasing of two struct member pointers passed to a function.`)
.params(u =>
u
.combine('address_space', kWritableAddressSpaces)
.combine('member', ['a', 'b'])
.combine('aliased', [true, false])
.beginSubcases()
.combine('a_use', keysOf(kUses))
.combine('b_use', keysOf(kUses))
)
.fn(t => {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
const code = `
struct S {
a : i32,
b : i32,
}
${maybeDeclareModuleScopeVar('x', t.params.address_space, 'S')}
${maybeDeclareModuleScopeVar('y', t.params.address_space, 'S')}
fn callee(pa : ${ptr(t.params.address_space, 'i32')},
pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
${kUses[t.params.a_use].gen(`*pa`)}
${kUses[t.params.b_use].gen(`*pb`)}
return 0;
}
fn caller() {
${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'S')}
${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'S')}
callee(&x.${t.params.member}, ${t.params.aliased ? `&x.a` : `&y.a`});
}
`;
t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
});
g.test('two_pointers_to_struct_members_indirect')
.desc(
`Test aliasing of two structure pointers passed to a function, which accesses members of those
structures and then passes the member pointers to another function.`
)
.params(u =>
u
.combine('address_space', kWritableAddressSpaces)
.combine('member', ['a', 'b'])
.combine('aliased', [true, false])
.beginSubcases()
.combine('a_use', keysOf(kUses))
.combine('b_use', keysOf(kUses))
)
.fn(t => {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
const code = `
struct S {
a : i32,
b : i32,
}
${maybeDeclareModuleScopeVar('x', t.params.address_space, 'S')}
${maybeDeclareModuleScopeVar('y', t.params.address_space, 'S')}
fn callee(pa : ${ptr(t.params.address_space, 'i32')},
pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
${kUses[t.params.a_use].gen(`*pa`)}
${kUses[t.params.b_use].gen(`*pb`)}
return 0;
}
fn access(pa : ${ptr(t.params.address_space, 'S')},
pb : ${ptr(t.params.address_space, 'S')}) -> i32 {
return callee(&(*pa).${t.params.member}, &(*pb).a);
}
fn caller() {
${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'S')}
${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'S')}
access(&x, ${t.params.aliased ? `&x` : `&y`});
}
`;
t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
});
g.test('one_pointer_one_module_scope')
.desc(`Test aliasing of a pointer with a direct access to a module-scope variable.`)
.params(u =>
u
.combine('address_space', ['private', 'storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
.combine('a_use', keysOf(kUses))
.combine('b_use', keysOf(kUses))
)
.fn(t => {
if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
}
const code = `
${declareModuleScopeVar('x', t.params.address_space, 'i32')}
${declareModuleScopeVar('y', t.params.address_space, 'i32')}
fn callee(pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
${kUses[t.params.a_use].gen(`x`)}
${kUses[t.params.b_use].gen(`*pb`)}
return 0;
}
fn caller() {
callee(${t.params.aliased ? `&x` : `&y`});
}
`;
t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
});
g.test('subcalls')
.desc(`Test aliasing of two pointers passed to a function, and then passed to other functions.`)
.params(u =>
u
.combine('address_space', ['private', 'storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
.combine('a_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
.combine('b_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
)
.fn(t => {
if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
}
const ptr_i32 = ptr(t.params.address_space, 'i32');
const code = `
${declareModuleScopeVar('x', t.params.address_space, 'i32')}
${declareModuleScopeVar('y', t.params.address_space, 'i32')}
fn subcall_no_access(p : ${ptr_i32}) {
let pp = &*p;
}
fn subcall_binary_lhs(p : ${ptr_i32}) -> i32 {
return *p + 1;
}
fn subcall_assign(p : ${ptr_i32}) {
*p = 42;
}
fn callee(pa : ${ptr_i32}, pb : ${ptr_i32}) -> i32 {
let new_pa = &*pa;
let new_pb = &*pb;
subcall_${t.params.a_use}(new_pa);
subcall_${t.params.b_use}(new_pb);
return 0;
}
fn caller() {
callee(&x, ${t.params.aliased ? `&x` : `&y`});
}
`;
t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
});
g.test('member_accessors')
.desc(`Test aliasing of two pointers passed to a function and used with member accessors.`)
.params(u =>
u
.combine('address_space', ['private', 'storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
.combine('a_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
.combine('b_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
)
.fn(t => {
if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
}
const ptr_S = ptr(t.params.address_space, 'S');
const code = `
struct S { a : i32 }
${declareModuleScopeVar('x', t.params.address_space, 'S')}
${declareModuleScopeVar('y', t.params.address_space, 'S')}
fn callee(pa : ${ptr_S}, pb : ${ptr_S}) -> i32 {
${kUses[t.params.a_use].gen(`(*pa).a`)}
${kUses[t.params.b_use].gen(`(*pb).a`)}
return 0;
}
fn caller() {
callee(&x, ${t.params.aliased ? `&x` : `&y`});
}
`;
t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
});
g.test('swizzles')
.desc(`Test aliasing of two pointers passed to a function and used with swizzles.`)
.params(u =>
u
.combine('address_space', ['private', 'storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
.combine('a_use', ['no_access', 'compound_assign_lhs'] as UseName[])
.combine('deref', [true, false])
)
.fn(t => {
if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
}
if (t.params.deref === false) {
t.skipIfLanguageFeatureNotSupported('pointer_composite_access');
}
const ptr_vec = ptr(t.params.address_space, 'vec4i');
const code = `
${declareModuleScopeVar('x', t.params.address_space, 'vec4i')}
${declareModuleScopeVar('y', t.params.address_space, 'vec4i')}
fn callee(pa : ${ptr_vec}, pb : ${ptr_vec}) -> i32 {
${kUses[t.params.a_use].gen(`(*pa)`)}
let value = ${t.params.deref ? `(*pb)` : `pb`}.wzyx;
return 0;
}
fn caller() {
callee(&x, ${t.params.aliased ? `&x` : `&y`});
}
`;
t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, 'let_init'), code);
});
g.test('same_pointer_read_and_write')
.desc(`Test that we can read from and write to the same pointer.`)
.params(u =>
u.combine('address_space', ['private', 'storage', 'workgroup'] as const).beginSubcases()
)
.fn(t => {
if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
}
const code = `
${declareModuleScopeVar('v', t.params.address_space, 'i32')}
fn callee(p : ${ptr(t.params.address_space, 'i32')}) {
*p = *p + 1;
}
fn caller() {
callee(&v);
}
`;
t.expectCompileResult(true, code);
});
g.test('aliasing_inside_function')
.desc(`Test that we can alias pointers inside a function.`)
.params(u =>
u.combine('address_space', ['private', 'storage', 'workgroup'] as const).beginSubcases()
)
.fn(t => {
const code = `
${declareModuleScopeVar('v', t.params.address_space, 'i32')}
fn foo() {
var v : i32;
let p1 = &v;
let p2 = &v;
*p1 = 42;
*p2 = 42;
}
`;
t.expectCompileResult(true, code);
});
const kAtomicBuiltins = [
'atomicLoad',
'atomicStore',
'atomicAdd',
'atomicSub',
'atomicMax',
'atomicMin',
'atomicAnd',
'atomicOr',
'atomicXor',
'atomicExchange',
'atomicCompareExchangeWeak',
] as const;
type AtomicBuiltins = (typeof kAtomicBuiltins)[number];
function isWrite(builtin: AtomicBuiltins) {
switch (builtin) {
case 'atomicLoad':
return false;
case 'atomicAdd':
case 'atomicSub':
case 'atomicMax':
case 'atomicMin':
case 'atomicAnd':
case 'atomicOr':
case 'atomicXor':
case 'atomicExchange':
case 'atomicCompareExchangeWeak':
case 'atomicStore':
return true;
}
}
function callAtomicBuiltin(builtin: AtomicBuiltins, ptr: string) {
switch (builtin) {
case 'atomicLoad':
return `i += ${builtin}(${ptr})`;
case 'atomicStore':
return `${builtin}(${ptr}, 42)`;
case 'atomicAdd':
case 'atomicSub':
case 'atomicMax':
case 'atomicMin':
case 'atomicAnd':
case 'atomicOr':
case 'atomicXor':
case 'atomicExchange':
return `i += ${builtin}(${ptr}, 42)`;
case 'atomicCompareExchangeWeak':
return `${builtin}(${ptr}, 10, 42)`;
}
}
g.test('two_atomic_pointers')
.desc(`Test aliasing of two atomic pointers passed to a function.`)
.params(u =>
u
.combine('builtin_a', kAtomicBuiltins)
.combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
.combine('address_space', ['storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
)
.fn(t => {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
const code = `
${declareModuleScopeVar('x', t.params.address_space, 'atomic<i32>')}
${declareModuleScopeVar('y', t.params.address_space, 'atomic<i32>')}
fn callee(pa : ${ptr_atomic_i32}, pb : ${ptr_atomic_i32}) {
var i : i32;
${callAtomicBuiltin(t.params.builtin_a, 'pa')};
${callAtomicBuiltin(t.params.builtin_b, 'pb')};
}
fn caller() {
callee(&x, &${t.params.aliased ? 'x' : 'y'});
}
`;
const shouldFail =
t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
t.expectCompileResult(!shouldFail, code);
});
g.test('two_atomic_pointers_to_array_elements')
.desc(`Test aliasing of two atomic array element pointers passed to a function.`)
.params(u =>
u
.combine('builtin_a', kAtomicBuiltins)
.combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
.combine('address_space', ['storage', 'workgroup'] as const)
.combine('index', [0, 1])
.combine('aliased', [true, false])
.beginSubcases()
)
.fn(t => {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
const code = `
${declareModuleScopeVar('x', t.params.address_space, 'array<atomic<i32>, 32>')}
${declareModuleScopeVar('y', t.params.address_space, 'array<atomic<i32>, 32>')}
fn callee(pa : ${ptr_atomic_i32}, pb : ${ptr_atomic_i32}) {
var i : i32;
${callAtomicBuiltin(t.params.builtin_a, 'pa')};
${callAtomicBuiltin(t.params.builtin_b, 'pb')};
}
fn caller() {
callee(&x[${t.params.index}], &${t.params.aliased ? 'x' : 'y'}[0]);
}
`;
const shouldFail =
t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
t.expectCompileResult(!shouldFail, code);
});
g.test('two_atomic_pointers_to_struct_members')
.desc(`Test aliasing of two struct member atomic pointers passed to a function.`)
.params(u =>
u
.combine('builtin_a', kAtomicBuiltins)
.combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
.combine('address_space', ['storage', 'workgroup'] as const)
.combine('member', ['a', 'b'])
.combine('aliased', [true, false])
.beginSubcases()
)
.fn(t => {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
const code = `
struct S {
a : atomic<i32>,
b : atomic<i32>,
}
${declareModuleScopeVar('x', t.params.address_space, 'S')}
${declareModuleScopeVar('y', t.params.address_space, 'S')}
fn callee(pa : ${ptr_atomic_i32}, pb : ${ptr_atomic_i32}) {
var i : i32;
${callAtomicBuiltin(t.params.builtin_a, 'pa')};
${callAtomicBuiltin(t.params.builtin_b, 'pb')};
}
fn caller() {
callee(&x.${t.params.member}, &${t.params.aliased ? 'x' : 'y'}.a);
}
`;
const shouldFail =
t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
t.expectCompileResult(!shouldFail, code);
});
g.test('one_atomic_pointer_one_module_scope')
.desc(`Test aliasing of an atomic pointer with a direct access to a module-scope variable.`)
.params(u =>
u
.combine('builtin_a', kAtomicBuiltins)
.combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
.combine('address_space', ['storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
)
.fn(t => {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
const code = `
${declareModuleScopeVar('x', t.params.address_space, 'atomic<i32>')}
${declareModuleScopeVar('y', t.params.address_space, 'atomic<i32>')}
fn callee(p : ${ptr_atomic_i32}) {
var i : i32;
${callAtomicBuiltin(t.params.builtin_a, 'p')};
${callAtomicBuiltin(t.params.builtin_b, t.params.aliased ? '&x' : '&y')};
}
fn caller() {
callee(&x);
}
`;
const shouldFail =
t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
t.expectCompileResult(!shouldFail, code);
});
g.test('workgroup_uniform_load')
.desc(`Test aliasing via workgroupUniformLoad.`)
.params(u =>
u
.combine('use', ['load', 'store', 'workgroupUniformLoad'] as const)
.combine('aliased', [true, false])
.beginSubcases()
)
.fn(t => {
t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
function emitUse() {
switch (t.params.use) {
case 'load':
return `v = *pa`;
case 'store':
return `*pa = 1`;
case 'workgroupUniformLoad':
return `v = workgroupUniformLoad(pa)`;
}
}
const code = `
var<workgroup> x : i32;
var<workgroup> y : i32;
fn callee(pa : ptr<workgroup, i32>, pb : ptr<workgroup, i32>) -> i32 {
var v : i32;
${emitUse()};
return v + workgroupUniformLoad(pb);
}
fn caller() {
callee(&x, &${t.params.aliased ? 'x' : 'y'});
}
`;
const shouldFail = t.params.aliased && t.params.use === 'store';
t.expectCompileResult(!shouldFail, code);
});