Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ import {
runReadLayoutTest,
runWriteLayoutTest,
runReadWriteTest,
kMixedTypeOps,
kMixedTypeIdx,
kMixedTypePairs,
kMixedTypeOverlaps,
kMixedTypeBuffers,
MixedType,
mixedTypeName,
mixedTypeWords,
runMixedTypeAliasingTest,
} from './buffer_view_utils.js';

export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
Expand Down Expand Up @@ -616,3 +625,74 @@ fn main() {

runReadWriteTest(false, t, wgsl, ele_ty, ty, t.params.aspace, t.params.offset, bufferSize);
});

g.test('mixed_types_aliasing')
.desc(
`Test that differently typed element pointers formed with bufferArrayView behave correctly when
they refer to overlapping bytes of a buffer, and both are formed and used within a single function.

Aliasing within a function is valid WGSL, so an implementation must not assume that differently
typed views do not alias (e.g. by applying C++ style type-based alias analysis).

* 'pair' selects the element types of the two views, covering combinations of scalars, vectors,
and structures (with and without padding), including f16 types.
* 'overlap' selects whether the elements start at the same byte, partially overlap, or only
overlap padding.
* 'access' selects how the element is located:
- 'constant_base': a constant byte offset, and element index 0.
- 'dynamic_base': a runtime byte offset, and element index 0.
- 'dynamic_index': a constant byte offset one element before the target, and a runtime element
index of 1. The two views therefore start at different offsets.
* If 'aliased' is false, the second view is formed on a different buffer.`
)
.params(u =>
u
.combine('buffer', kMixedTypeBuffers)
.combine('pair', keysOf(kMixedTypePairs))
.combine('overlap', kMixedTypeOverlaps)
.filter(t => {
const pair = kMixedTypePairs[t.pair];
return mixedTypeWords(pair.int, pair.other, t.overlap) !== undefined;
})
.combine('access', ['constant_base', 'dynamic_base', 'dynamic_index'] as const)
.combine('aliased', [true, false])
.beginSubcases()
.combine('op', keysOf(kMixedTypeOps))
)
.fn(t => {
const pair = kMixedTypePairs[t.params.pair];
const words = mixedTypeWords(pair.int, pair.other, t.params.overlap)!;
// The dynamic base adds 'input.p[3] * 16' bytes, which preserves the alignment of all types.
const dynamicWords = 4 * kMixedTypeIdx;
const view = (type: MixedType, buffer: string, word: number) => {
const ty = mixedTypeName(type);
// The element stride in bytes. All of the tested types have a stride equal to their size.
const stride = type.size;
let base = '';
let index = '0';
switch (t.params.access) {
case 'constant_base':
base = `${word * 4}u`;
break;
case 'dynamic_base':
base = `${(word - dynamicWords) * 4}u + u32(input.p[3]) * 16u`;
break;
case 'dynamic_index':
base = `${word * 4 - stride * kMixedTypeIdx}u`;
index = 'input.p[3]';
break;
}
// Each view spans 32 bytes, which keeps all views within the buffer.
return `&(*bufferArrayView<array<${ty}>>(&${buffer}, ${base}, 32u))[${index}]`;
};
runMixedTypeAliasingTest(t, {
buffer: t.params.buffer,
int: pair.int,
other: pair.other,
intWord: words.intWord,
otherWord: words.otherWord,
view,
aliased: t.params.aliased,
op: kMixedTypeOps[t.params.op],
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ import {
runReadLayoutTest,
runWriteLayoutTest,
runReadWriteTest,
kMixedTypeOps,
kMixedTypeIdx,
kMixedTypePairs,
kMixedTypeOverlaps,
kMixedTypeBuffers,
MixedType,
mixedTypeName,
mixedTypeWords,
runMixedTypeAliasingTest,
} from './buffer_view_utils.js';

export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
Expand Down Expand Up @@ -582,3 +591,55 @@ fn main() {

runReadWriteTest(false, t, wgsl, ele_ty, ty, t.params.aspace, t.params.offset, bufferSize);
});

g.test('mixed_types_aliasing')
.desc(
`Test that differently typed pointers formed with bufferView behave correctly when they are views
of overlapping bytes of a buffer, and both are formed and used within a single function.

Aliasing within a function is valid WGSL, so an implementation must not assume that differently
typed views do not alias (e.g. by applying C++ style type-based alias analysis).

* 'pair' selects the types of the two views, covering combinations of scalars, vectors, and
structures (with and without padding), including f16 types.
* 'overlap' selects whether the views start at the same byte, partially overlap, or only overlap
padding.
* 'offset' selects whether the byte offset passed to bufferView is a constant or a runtime value.
* If 'aliased' is false, the second view is formed on a different buffer.`
)
.params(u =>
u
.combine('buffer', kMixedTypeBuffers)
.combine('pair', keysOf(kMixedTypePairs))
.combine('overlap', kMixedTypeOverlaps)
.filter(t => {
const pair = kMixedTypePairs[t.pair];
return mixedTypeWords(pair.int, pair.other, t.overlap) !== undefined;
})
.combine('offset', ['constant', 'dynamic'] as const)
.combine('aliased', [true, false])
.beginSubcases()
.combine('op', keysOf(kMixedTypeOps))
)
.fn(t => {
const pair = kMixedTypePairs[t.params.pair];
const words = mixedTypeWords(pair.int, pair.other, t.params.overlap)!;
// The dynamic offset adds 'input.p[3] * 16' bytes, which preserves the alignment of all types.
const dynamicWords = 4 * kMixedTypeIdx;
const view = (type: MixedType, buffer: string, word: number) => {
const ty = mixedTypeName(type);
return t.params.offset === 'constant'
? `bufferView<${ty}>(&${buffer}, ${word * 4}u)`
: `bufferView<${ty}>(&${buffer}, ${(word - dynamicWords) * 4}u + u32(input.p[3]) * 16u)`;
};
runMixedTypeAliasingTest(t, {
buffer: t.params.buffer,
int: pair.int,
other: pair.other,
intWord: words.intWord,
otherWord: words.otherWord,
view,
aliased: t.params.aliased,
op: kMixedTypeOps[t.params.op],
});
});
Loading
Loading