| 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); |
| }); |