blob: 18dbff0419f9610db0ada1c7670ef3014d02efbd [file] [log] [blame]
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);
});
const kLHSExpressions = {
deref_invalid1: { expr: `*p.b`, result: false },
deref_invalid2: { expr: `*p.a[0]`, result: false },
deref_valid1: { expr: `(*p).b`, result: true },
deref_valid2: { expr: `(*p).a[2]`, result: true },
addr_valid1: { expr: `*&v.b`, result: true },
addr_valid2: { expr: `(*&v).b`, result: true },
addr_valid3: { expr: `*&(v.b)`, result: true },
};
g.test('other_lhs')
.desc('Test precedence of * and [] in LHS')
.params(u => u.combine('expr', keysOf(kLHSExpressions)))
.fn(t => {
const expr = kLHSExpressions[t.params.expr];
const code = `
struct S {
a : array<i32, 4>,
b : i32,
}
fn main() {
var v : S;
let p = &v;
let q = &v.a;
${expr.expr} = 1i;
}`;
t.expectCompileResult(expr.result, code);
});