summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts188
1 files changed, 188 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts
new file mode 100644
index 0000000000..15f7ad4a97
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts
@@ -0,0 +1,188 @@
+export const description = `
+Validation tests for operator precedence.
+`;
+
+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);
+
+// Bit set for the binary operator groups.
+const kMultiplicative = 1 << 0;
+const kAdditive = 1 << 1;
+const kShift = 1 << 2;
+const kRelational = 1 << 3;
+const kBinaryAnd = 1 << 4;
+const kBinaryXor = 1 << 5;
+const kBinaryOr = 1 << 6;
+const kLogical = 1 << 7;
+
+// Set of other operators that each operator can precede without any parentheses.
+const kCanPrecedeWithoutParens: Record<number, number> = {};
+kCanPrecedeWithoutParens[kMultiplicative] = kMultiplicative | kAdditive | kRelational;
+kCanPrecedeWithoutParens[kAdditive] = kMultiplicative | kAdditive | kRelational;
+kCanPrecedeWithoutParens[kShift] = kRelational | kLogical;
+kCanPrecedeWithoutParens[kRelational] = kMultiplicative | kAdditive | kShift | kLogical;
+kCanPrecedeWithoutParens[kBinaryAnd] = kBinaryAnd;
+kCanPrecedeWithoutParens[kBinaryXor] = kBinaryXor;
+kCanPrecedeWithoutParens[kBinaryOr] = kBinaryOr;
+kCanPrecedeWithoutParens[kLogical] = kRelational;
+
+// The list of binary operators.
+interface BinaryOperatorInfo {
+ op: string;
+ group: number;
+}
+const kBinaryOperators: Record<string, BinaryOperatorInfo> = {
+ mul: { op: '*', group: kMultiplicative },
+ div: { op: '/', group: kMultiplicative },
+ mod: { op: '%', group: kMultiplicative },
+
+ add: { op: '+', group: kAdditive },
+ sub: { op: '-', group: kAdditive },
+
+ shl: { op: '<<', group: kShift },
+ shr: { op: '>>', group: kShift },
+
+ lt: { op: '<', group: kRelational },
+ gt: { op: '>', group: kRelational },
+ le: { op: '<=', group: kRelational },
+ ge: { op: '>=', group: kRelational },
+ eq: { op: '==', group: kRelational },
+ ne: { op: '!=', group: kRelational },
+
+ bin_and: { op: '&', group: kBinaryAnd },
+ bin_xor: { op: '^', group: kBinaryXor },
+ bin_or: { op: '|', group: kBinaryOr },
+
+ log_and: { op: '&&', group: kLogical },
+ log_or: { op: '||', group: kLogical },
+};
+
+g.test('binary_requires_parentheses')
+ .desc(
+ `
+ Validates that certain binary operators require parentheses to bind correctly.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op1', keysOf(kBinaryOperators))
+ .combine('op2', keysOf(kBinaryOperators))
+ .filter(p => {
+ // Skip expressions that would parse as template lists.
+ if (p.op1 === 'lt' && ['gt', 'ge', 'shr'].includes(p.op2)) {
+ return false;
+ }
+ // Only combine logical operators with relational operators.
+ if (kBinaryOperators[p.op1].group === kLogical) {
+ return kBinaryOperators[p.op2].group === kRelational;
+ }
+ if (kBinaryOperators[p.op2].group === kLogical) {
+ return kBinaryOperators[p.op1].group === kRelational;
+ }
+ return true;
+ })
+ )
+ .fn(t => {
+ const op1 = kBinaryOperators[t.params.op1];
+ const op2 = kBinaryOperators[t.params.op2];
+ const code = `
+var<private> a : ${op1.group === kLogical ? 'bool' : 'u32'};
+var<private> b : u32;
+var<private> c : ${op2.group === kLogical ? 'bool' : 'u32'};
+fn foo() {
+ let foo = a ${op1.op} b ${op2.op} c;
+}
+`;
+
+ const valid = (kCanPrecedeWithoutParens[op1.group] & op2.group) !== 0;
+ t.expectCompileResult(valid, code);
+ });
+
+g.test('mixed_logical_requires_parentheses')
+ .desc(
+ `
+ Validates that mixed logical operators require parentheses to bind correctly.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op1', keysOf(kBinaryOperators))
+ .combine('op2', keysOf(kBinaryOperators))
+ .combine('parens', ['none', 'left', 'right'])
+ .filter(p => {
+ const group1 = kBinaryOperators[p.op1].group;
+ const group2 = kBinaryOperators[p.op2].group;
+ return group1 === kLogical && group2 === kLogical;
+ })
+ )
+ .fn(t => {
+ const op1 = kBinaryOperators[t.params.op1];
+ const op2 = kBinaryOperators[t.params.op2];
+ let expr = `a ${op1.op} b ${op2.op} c;`;
+ if (t.params.parens === 'left') {
+ expr = `(a ${op1.op} b) ${op2.op} c;`;
+ } else if (t.params.parens === 'right') {
+ expr = `a ${op1.op} (b ${op2.op} c);`;
+ }
+ const code = `
+var<private> a : bool;
+var<private> b : bool;
+var<private> c : bool;
+fn foo() {
+ let bar = ${expr};
+}
+`;
+ const valid = t.params.parens !== 'none' || t.params.op1 === t.params.op2;
+ t.expectCompileResult(valid, code);
+ });
+
+// The list of miscellaneous other test cases.
+interface Expression {
+ expr: string;
+ result: boolean;
+}
+const kExpressions: Record<string, Expression> = {
+ neg_member: { expr: '- str . a', result: true },
+ comp_member: { expr: '~ str . a', result: true },
+ addr_member: { expr: '& str . a', result: true },
+ log_and_member: { expr: 'false && str . b', result: true },
+ log_or_member: { expr: 'false || str . b', result: true },
+ and_addr: { expr: ' v & &str .a', result: false },
+ and_addr_paren: { expr: 'v & (&str).a', result: true },
+ deref_member: { expr: ' * ptr_str . a', result: false },
+ deref_member_paren: { expr: '(* ptr_str) . a', result: true },
+ deref_idx: { expr: ' * ptr_vec [0]', result: false },
+ deref_idx_paren: { expr: '(* ptr_vec) [1]', result: true },
+};
+
+g.test('other')
+ .desc(
+ `
+ Test that other operator precedence rules are correctly implemented.
+ `
+ )
+ .params(u => u.combine('expr', keysOf(kExpressions)))
+ .fn(t => {
+ const expr = kExpressions[t.params.expr];
+ const wgsl = `
+ struct S {
+ a: i32,
+ b: bool,
+ }
+
+ fn main() {
+ var v = 42;
+ var vec = vec4();
+ var str = S(42, false);
+ let ptr_vec = &vec;
+ let ptr_str = &str;
+
+ let foo = ${expr.expr};
+ }
+ `;
+
+ t.expectCompileResult(expr.result, wgsl);
+ });