use super::*;
#[test]
fn every_return_is_preceded_by_a_grid_sync_at_the_same_top_level() {
let max_iterations = 6u32;
let program = persistent_fixpoint_grid(
or_const_body(4, 0b1010),
"current",
"next",
"changed",
4,
max_iterations,
);
let waves = wave_nodes(&program);
let mut grid_syncs_seen = 0usize;
let mut returns_seen = 0usize;
for (index, node) in waves.iter().enumerate() {
if matches!(
node,
Node::Barrier {
ordering: MemoryOrdering::GridSync
}
) {
grid_syncs_seen += 1;
}
if any_descendant(node, &mut |candidate| matches!(candidate, Node::Return)) {
assert!(
grid_syncs_seen > 0,
"top-level node {index} carries a Return with no preceding GridSync: the exit \
would be decided per group and a departing group would strand the rest"
);
returns_seen += 1;
assert_eq!(
grid_syncs_seen % 2,
0,
"the Return at top-level node {index} must follow the wave's SECOND GridSync \
(the one after the compare step), not sit between the two"
);
}
}
assert_eq!(
returns_seen, max_iterations as usize,
"each wave must carry exactly one collective exit"
);
assert_eq!(
count_nodes(&program, |node| matches!(node, Node::Return)),
max_iterations as usize,
"no Return may hide anywhere else in the tree"
);
}
#[test]
fn changed_words_are_only_atomic_ored_and_never_cleared() {
let max_iterations = 5u32;
let program = persistent_fixpoint_grid(
or_const_body(4, 0b1010),
"current",
"next",
"changed",
4,
max_iterations,
);
assert_eq!(
count_nodes(&program, |node| matches!(
node,
Node::Store { buffer, .. } if buffer.as_str() == "changed"
)),
0,
"a plain store to the flag buffer reintroduces the clear-versus-set race"
);
let indices = atomic_or_indices(&program, "changed");
assert_eq!(
indices,
(0..max_iterations).collect::<Vec<u32>>(),
"wave i must own flag word i exclusively; a shared or reused word is the lost-set race"
);
let flag_decl = program
.buffers
.iter()
.find(|buffer| buffer.name() == "changed")
.expect("flag buffer must be declared");
assert_eq!(
flag_decl.count(),
max_iterations,
"the flag buffer must be one word per iteration"
);
assert_eq!(
flag_decl.binding(),
2,
"flag binding must stay at 2 so callers keep reading outputs positionally"
);
}
#[test]
fn wave_fences_are_grid_sync_and_never_workgroup_scope() {
let max_iterations = 7u32;
let program = persistent_fixpoint_grid(
carry_body(4),
"current",
"next",
"changed",
4,
max_iterations,
);
assert_eq!(
count_nodes(&program, |node| matches!(
node,
Node::Barrier {
ordering: MemoryOrdering::GridSync
}
)),
2 * max_iterations as usize,
"each wave needs two grid-wide fences: one after the transfer, one after the compare"
);
assert_eq!(
count_nodes(
&program,
|node| matches!(node, Node::Barrier { ordering } if !matches!(
ordering,
MemoryOrdering::GridSync
))
),
0,
"a workgroup-scope fence between waves orders nothing across groups"
);
assert_eq!(
count_nodes(&program, |node| matches!(node, Node::Loop { .. })),
0,
"the wave form must not fall back to an in-kernel convergence loop"
);
}
#[test]
fn exactly_max_iterations_waves_are_emitted() {
for max_iterations in [1u32, 2, 3, 8, 15] {
let program = persistent_fixpoint_grid(
or_const_body(4, 0b1010),
"current",
"next",
"changed",
4,
max_iterations,
);
let waves = wave_nodes(&program);
assert_eq!(
waves.len(),
5 * max_iterations as usize,
"each wave is exactly five top-level nodes: transfer, fence, compare, fence, exit"
);
assert_eq!(
atomic_or_indices(&program, "changed"),
(0..max_iterations).collect::<Vec<u32>>(),
"flag words must cover 0..max_iterations with no gap and no repeat"
);
assert_eq!(
count_nodes(&program, |node| matches!(node, Node::Return)),
max_iterations as usize,
"one collective exit per wave"
);
assert_eq!(
count_nodes(&program, |node| matches!(
node,
Node::Barrier {
ordering: MemoryOrdering::GridSync
}
)),
2 * max_iterations as usize,
"two grid fences per wave"
);
}
}
#[test]
fn zero_budget_emits_no_waves_and_a_floored_flag_buffer() {
let flag_count = |program: &Program| {
program
.buffers
.iter()
.find(|buffer| buffer.name() == "changed")
.expect("flag buffer must be declared")
.count()
};
let build = |max_iterations: u32| {
persistent_fixpoint_grid(
or_const_body(4, 0b1010),
"current",
"next",
"changed",
4,
max_iterations,
)
};
let zero = build(0);
let three = build(3);
assert_eq!(
wave_nodes(&zero).len(),
0,
"a zero budget must emit exactly no wave nodes"
);
assert_eq!(
wave_nodes(&three).len(),
5 * 3,
"the same builder must still emit five nodes per wave, so the zero \
case above is a real floor and not a builder that emits nothing"
);
assert_eq!(
flag_count(&zero),
1,
"the flag buffer count must be floored at one word"
);
assert_eq!(
flag_count(&three),
3,
"a nonzero budget must declare one flag word per iteration, so the \
floor above is a floor and not a hardcoded one-word buffer"
);
}
#[test]
fn repeated_transfer_body_with_top_level_bindings_stays_valid_ir() {
let body = vec![
Node::let_bind("carried", Expr::load("current", lane())),
Node::if_then(
Expr::lt(lane(), Expr::u32(4)),
vec![Node::store(
"next",
lane(),
Expr::bitor(Expr::var("carried"), Expr::u32(0b1010)),
)],
),
];
let program = persistent_fixpoint_grid(body, "current", "next", "changed", 4, 4);
let errors = vyre_foundation::ir::validate(&program);
assert!(
errors.is_empty(),
"grid wave form must be valid IR, got: {errors:?}"
);
}