Skip to content

Commit

Permalink
shader/validation: Add ptr aliasing analysis for UPP
Browse files Browse the repository at this point in the history
`unrestricted_pointer_parameters` unlocks pointer aliasing via builtins that was not possible before.
  • Loading branch information
ben-clayton committed Jan 24, 2024
1 parent 7abda1d commit 9c4d660
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/webgpu/listing_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
233 changes: 233 additions & 0 deletions src/webgpu/shader/validation/functions/alias_analysis.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<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);
});

0 comments on commit 9c4d660

Please sign in to comment.