diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index 8380593d58bd..01aa3713db32 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -1834,14 +1834,19 @@ "webgpu:shader,validation,extension,pointer_composite_access:pointer:*": { "subcaseMS": 0.0 }, "webgpu:shader,validation,functions,alias_analysis:aliasing_inside_function:*": { "subcaseMS": 1.200 }, "webgpu:shader,validation,functions,alias_analysis:member_accessors:*": { "subcaseMS": 1.656 }, + "webgpu:shader,validation,functions,alias_analysis:one_atomic_pointer_one_module_scope:*": { "subcaseMS": 0 }, "webgpu:shader,validation,functions,alias_analysis:one_pointer_one_module_scope:*": { "subcaseMS": 1.598 }, "webgpu:shader,validation,functions,alias_analysis:same_pointer_read_and_write:*": { "subcaseMS": 1.301 }, "webgpu:shader,validation,functions,alias_analysis:subcalls:*": { "subcaseMS": 1.673 }, + "webgpu:shader,validation,functions,alias_analysis:two_atomic_pointers_to_array_elements:*": { "subcaseMS": 0 }, + "webgpu:shader,validation,functions,alias_analysis:two_atomic_pointers_to_struct_members:*": { "subcaseMS": 0 }, + "webgpu:shader,validation,functions,alias_analysis:two_atomic_pointers:*": { "subcaseMS": 0 }, "webgpu:shader,validation,functions,alias_analysis:two_pointers_to_array_elements_indirect:*": { "subcaseMS": 0 }, "webgpu:shader,validation,functions,alias_analysis:two_pointers_to_array_elements:*": { "subcaseMS": 0 }, "webgpu:shader,validation,functions,alias_analysis:two_pointers_to_struct_members_indirect:*": { "subcaseMS": 0 }, "webgpu:shader,validation,functions,alias_analysis:two_pointers_to_struct_members:*": { "subcaseMS": 0 }, "webgpu:shader,validation,functions,alias_analysis:two_pointers:*": { "subcaseMS": 1.537 }, + "webgpu:shader,validation,functions,alias_analysis:workgroup_uniform_load:*": { "subcaseMS": 0 }, "webgpu:shader,validation,functions,restrictions:call_arg_types_match_params:*": { "subcaseMS": 1.518 }, "webgpu:shader,validation,functions,restrictions:entry_point_call_target:*": { "subcaseMS": 1.734 }, "webgpu:shader,validation,functions,restrictions:function_parameter_matching:*": { "subcaseMS": 1.953 }, diff --git a/src/webgpu/shader/validation/functions/alias_analysis.spec.ts b/src/webgpu/shader/validation/functions/alias_analysis.spec.ts index 6b267b67b78b..faca240f0f7d 100644 --- a/src/webgpu/shader/validation/functions/alias_analysis.spec.ts +++ b/src/webgpu/shader/validation/functions/alias_analysis.spec.ts @@ -452,3 +452,236 @@ fn foo() { `; 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'); + const code = ` +${declareModuleScopeVar('x', t.params.address_space, 'atomic')} +${declareModuleScopeVar('y', t.params.address_space, 'atomic')} + +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'); + const code = ` +${declareModuleScopeVar('x', t.params.address_space, 'array, 32>')} +${declareModuleScopeVar('y', t.params.address_space, 'array, 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'); + const code = ` +struct S { + a : atomic, + b : atomic, +} + +${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'); + const code = ` +${declareModuleScopeVar('x', t.params.address_space, 'atomic')} +${declareModuleScopeVar('y', t.params.address_space, 'atomic')} + +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 x : i32; +var y : i32; + +fn callee(pa : ptr, pb : ptr) -> 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); + });