export const description = `
Flow control tests for expression evaluation order.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import { runFlowControlTest } from './harness.js';
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
g.test('binary_op')
.desc('Test that a binary operator evaluates the LHS then the RHS')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = lhs() + rhs();
${f.expect_order(3)}
`,
extra: `
fn lhs() -> i32 {
${f.expect_order(1)}
return 0;
}
fn rhs() -> i32 {
${f.expect_order(2)}
return 0;
}`,
}));
});
g.test('binary_op_rhs_const')
.desc('Test that a binary operator evaluates the LHS, when the RHS is a constant expression')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = lhs() + rhs();
${f.expect_order(2)}
`,
extra: `
fn lhs() -> i32 {
${f.expect_order(1)}
return 0;
}
fn rhs() -> i32 {
return 0;
}`,
}));
});
g.test('binary_op_lhs_const')
.desc('Test that a binary operator evaluates the RHS, when the LHS is a constant expression')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = lhs() + rhs();
${f.expect_order(2)}
`,
extra: `
fn lhs() -> i32 {
return 0;
}
fn rhs() -> i32 {
${f.expect_order(1)}
return 0;
}`,
}));
});
g.test('binary_op_chain')
.desc('Test that a binary operator chain evaluates left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = a() + b() - c() * d();
${f.expect_order(5)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 1;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4)}
return 1;
}`,
}));
});
g.test('binary_op_chain_R_C_C_C')
.desc(
'Test evaluation order of a binary operator chain with a runtime-expression for the left-most expression'
)
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = f() + 1 + 2 + 3;
${f.expect_order(2)}
`,
extra: `
fn f() -> i32 {
${f.expect_order(1)}
return 1;
}
`,
}));
});
g.test('binary_op_chain_C_R_C_C')
.desc(
'Test evaluation order of a binary operator chain with a runtime-expression for the second-left-most-const'
)
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = 1 + f() + 2 + 3;
${f.expect_order(2)}
`,
extra: `
fn f() -> i32 {
${f.expect_order(1)}
return 1;
}
`,
}));
});
g.test('binary_op_chain_C_C_R_C')
.desc(
'Test evaluation order of a binary operator chain with a runtime-expression for the second-right-most-const'
)
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = 1 + 2 + f() + 3;
${f.expect_order(2)}
`,
extra: `
fn f() -> i32 {
${f.expect_order(1)}
return 1;
}
`,
}));
});
g.test('binary_op_chain_C_C_C_R')
.desc(
'Test evaluation order of a binary operator chain with a runtime-expression for the right-most expression'
)
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = 1 + 2 + 3 + f();
${f.expect_order(2)}
`,
extra: `
fn f() -> i32 {
${f.expect_order(1)}
return 1;
}
`,
}));
});
g.test('binary_op_parenthesized_expr')
.desc('Test that a parenthesized binary operator expression evaluates left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let x = (a() + b()) - (c() * d());
${f.expect_order(5)}
let y = a() + (b() - c()) * d();
${f.expect_order(10)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1, 6)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2, 7)}
return 1;
}
fn c() -> i32 {
${f.expect_order(3, 8)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4, 9)}
return 1;
}`,
}));
});
g.test('array_index')
.desc('Test that array indices are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var arr : array<array<array<i32, 8>, 8>, 8>;
${f.expect_order(0)}
let x = arr[a()][b()][c()];
${f.expect_order(4)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 1;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}`,
}));
});
g.test('array_index_lhs_assignment')
.desc(
'Test that array indices are evaluated left-to-right, when indexing the LHS of an assignment'
)
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var arr : array<array<array<i32, 8>, 8>, 8>;
${f.expect_order(0)}
arr[a()][b()][c()] = ~d();
${f.expect_order(5)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 1;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4)}
return 1;
}`,
}));
});
g.test('array_index_lhs_member_assignment')
.desc(
'Test that array indices are evaluated left-to-right, when indexing with member-accessors in the LHS of an assignment'
)
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var arr : array<array<S, 8>, 8>;
${f.expect_order(0)}
arr[a()][b()].member[c()] = d();
${f.expect_order(5)}
`,
extra: `
struct S {
member : array<i32, 8>,
}
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 1;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4)}
return 1;
}`,
}));
});
g.test('array_index_via_ptrs')
.desc('Test that array indices are evaluated in order, when used via pointers')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var arr : array<array<array<i32, 8>, 8>, 8>;
${f.expect_order(0)}
let p0 = &arr;
${f.expect_order(1)}
let p1 = &(*p0)[a()];
${f.expect_order(3)}
let p2 = &(*p1)[b()];
${f.expect_order(5)}
let p3 = &(*p2)[c()];
${f.expect_order(7)}
let p4 = *p3;
`,
extra: `
fn a() -> i32 {
${f.expect_order(2)}
return 1;
}
fn b() -> i32 {
${f.expect_order(4)}
return 1;
}
fn c() -> i32 {
${f.expect_order(6)}
return 1;
}`,
}));
});
g.test('array_index_via_struct_members')
.desc('Test that array indices are evaluated in order, when accessed via structure members')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var x : X;
${f.expect_order(0)}
let r = x.y[a()].z[b()].a[c()];
${f.expect_order(4)}
`,
extra: `
struct X {
y : array<Y, 3>,
};
struct Y {
z : array<Z, 3>,
};
struct Z {
a : array<i32, 3>,
};
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 1;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}`,
}));
});
g.test('matrix_index')
.desc('Test that matrix indices are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var mat : mat4x4<f32>;
${f.expect_order(0)}
let x = mat[a()][b()];
${f.expect_order(3)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 1;
}`,
}));
});
g.test('matrix_index_via_ptr')
.desc('Test that matrix indices are evaluated in order, when used via pointers')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var mat : mat4x4<f32>;
${f.expect_order(0)}
let p0 = &mat;
${f.expect_order(1)}
let p1 = &(*p0)[a()];
${f.expect_order(3)}
let v = (*p1)[b()];
${f.expect_order(5)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(2)}
return 1;
}
fn b() -> i32 {
${f.expect_order(4)}
return 1;
}`,
}));
});
g.test('logical_and')
.desc(
'Test that a chain of logical-AND expressions are evaluated left-to-right, stopping at the first false'
)
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = a() && b() && c();
${f.expect_order(3)}
`,
extra: `
fn a() -> bool {
${f.expect_order(1)}
return true;
}
fn b() -> bool {
${f.expect_order(2)}
return false;
}
fn c() -> bool {
${f.expect_not_reached()}
return true;
}
`,
}));
});
g.test('logical_or')
.desc(
'Test that a chain of logical-OR expressions are evaluated left-to-right, stopping at the first true'
)
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = a() || b() || c();
${f.expect_order(3)}
`,
extra: `
fn a() -> bool {
${f.expect_order(1)}
return false;
}
fn b() -> bool {
${f.expect_order(2)}
return true;
}
fn c() -> bool {
${f.expect_not_reached()}
return true;
}
`,
}));
});
g.test('bitwise_and')
.desc(
'Test that a chain of bitwise-AND expressions are evaluated left-to-right, with no short-circuiting'
)
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = a() & b() & c();
${f.expect_order(4)}
`,
extra: `
fn a() -> bool {
${f.expect_order(1)}
return true;
}
fn b() -> bool {
${f.expect_order(2)}
return false;
}
fn c() -> bool {
${f.expect_order(3)}
return true;
}
`,
}));
});
g.test('bitwise_or')
.desc(
'Test that a chain of bitwise-OR expressions are evaluated left-to-right, with no short-circuiting'
)
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = a() | b() | c();
${f.expect_order(4)}
`,
extra: `
fn a() -> bool {
${f.expect_order(1)}
return false;
}
fn b() -> bool {
${f.expect_order(2)}
return true;
}
fn c() -> bool {
${f.expect_order(3)}
return true;
}
`,
}));
});
g.test('user_fn_args')
.desc('Test user function call arguments are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = f(a(), b(), c());
${f.expect_order(5)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 2;
}
fn c() -> i32 {
${f.expect_order(3)}
return 3;
}
fn f(x : i32, y : i32, z : i32) -> i32 {
${f.expect_order(4)}
return x + y + z;
}`,
}));
});
g.test('nested_fn_args')
.desc('Test user nested call arguments are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = g(c(a(), b()), f(d(), e()));
${f.expect_order(8)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 0;
}
fn b() -> i32 {
${f.expect_order(2)}
return 0;
}
fn c(x : i32, y : i32) -> i32 {
${f.expect_order(3)}
return x + y;
}
fn d() -> i32 {
${f.expect_order(4)}
return 0;
}
fn e() -> i32 {
${f.expect_order(5)}
return 0;
}
fn f(x : i32, y : i32) -> i32 {
${f.expect_order(6)}
return x + y;
}
fn g(x : i32, y : i32) -> i32 {
${f.expect_order(7)}
return x + y;
}`,
}));
});
g.test('builtin_fn_args')
.desc('Test builtin function call arguments are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = mix(a(), b(), c());
${f.expect_order(4)}
`,
extra: `
fn a() -> f32 {
${f.expect_order(1)}
return 1;
}
fn b() -> f32 {
${f.expect_order(2)}
return 2;
}
fn c() -> f32 {
${f.expect_order(3)}
return 3;
}
`,
}));
});
g.test('nested_builtin_fn_args')
.desc('Test nested builtin function call arguments are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let l = mix(a(), mix(b(), c(), d()), e());
${f.expect_order(6)}
`,
extra: `
fn a() -> f32 {
${f.expect_order(1)}
return 1;
}
fn b() -> f32 {
${f.expect_order(2)}
return 2;
}
fn c() -> f32 {
${f.expect_order(3)}
return 3;
}
fn d() -> f32 {
${f.expect_order(4)}
return 3;
}
fn e() -> f32 {
${f.expect_order(5)}
return 3;
}
`,
}));
});
g.test('1d_array_constructor')
.desc('Test arguments of an array constructor are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let v = array(a(), b(), c(), d());
${f.expect_order(5)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 2;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4)}
return 2;
}
`,
}));
});
g.test('2d_array_constructor')
.desc('Test arguments of a 2D array constructor are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let v = array(array(a(), b()), array(c(), d()));
${f.expect_order(5)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 2;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4)}
return 2;
}
`,
}));
});
g.test('vec4_constructor')
.desc('Test arguments of a vector constructor are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let v = vec4(a(), b(), c(), d());
${f.expect_order(5)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 2;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4)}
return 2;
}
`,
}));
});
g.test('nested_vec4_constructor')
.desc('Test arguments of a nested vector constructor are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let v = vec4(a(), vec2(b(), c()), d());
${f.expect_order(5)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 2;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4)}
return 2;
}
`,
}));
});
g.test('struct_constructor')
.desc('Test arguments of a structure constructor are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let v = S(a(), b(), c(), d());
${f.expect_order(5)}
`,
extra: `
struct S {
a : i32,
b : i32,
c : i32,
d : i32,
}
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 2;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4)}
return 2;
}
`,
}));
});
g.test('nested_struct_constructor')
.desc('Test arguments of a nested structure constructor are evaluated left-to-right')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
${f.expect_order(0)}
let v = Y(a(), X(b(), c()), d());
${f.expect_order(5)}
`,
extra: `
struct Y {
a : i32,
x : X,
c : i32,
}
struct X {
b : i32,
c : i32,
}
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 2;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4)}
return 2;
}
`,
}));
});
g.test('1d_array_assignment')
.desc('Test LHS of an array element assignment is evaluated before RHS')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var arr : array<i32, 8>;
${f.expect_order(0)}
arr[a()] = arr[b()];
${f.expect_order(3)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 2;
}
`,
}));
});
g.test('2d_array_assignment')
.desc('Test LHS of 2D-array element assignment is evaluated before RHS')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var arr : array<array<i32, 8>, 8>;
${f.expect_order(0)}
arr[a()][b()] = arr[c()][d()];
${f.expect_order(5)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 2;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4)}
return 2;
}
`,
}));
});
g.test('1d_array_compound_assignment')
.desc('Test LHS of an array element compound assignment is evaluated before RHS')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var arr : array<i32, 8>;
${f.expect_order(0)}
arr[a()] += arr[b()];
${f.expect_order(3)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 2;
}
`,
}));
});
g.test('2d_array_compound_assignment')
.desc('Test LHS of a 2D-array element compound assignment is evaluated before RHS')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var arr : array<array<i32, 8>, 8>;
${f.expect_order(0)}
arr[a()][b()] += arr[c()][d()];
${f.expect_order(5)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 2;
}
fn c() -> i32 {
${f.expect_order(3)}
return 1;
}
fn d() -> i32 {
${f.expect_order(4)}
return 2;
}
`,
}));
});
g.test('1d_array_increment')
.desc('Test index of an array element increment is evaluated only once')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var arr : array<i32, 8>;
${f.expect_order(0)}
arr[a()]++;
${f.expect_order(2)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
`,
}));
});
g.test('2d_array_increment')
.desc('Test index of a 2D-array element increment is evaluated only once')
.fn(t => {
runFlowControlTest(t, f => ({
entrypoint: `
var arr : array<array<i32, 8>, 8>;
${f.expect_order(0)}
arr[a()][b()]++;
${f.expect_order(3)}
`,
extra: `
fn a() -> i32 {
${f.expect_order(1)}
return 1;
}
fn b() -> i32 {
${f.expect_order(2)}
return 1;
}
`,
}));
});