use vyre_foundation::ir::{Node, Program};
use vyre_primitives::fixpoint::persistent_fixpoint::{
persistent_fixpoint, persistent_fixpoint_grid, PERSISTENT_FIXPOINT_WORKGROUP_SIZE,
};
#[must_use]
pub fn persistent_fixpoint_program(
transfer_body: Vec<Node>,
current: &str,
next: &str,
changed: &str,
words: u32,
max_iterations: u32,
) -> Program {
if words > PERSISTENT_FIXPOINT_WORKGROUP_SIZE[0] {
return persistent_fixpoint_grid(
transfer_body,
current,
next,
changed,
words,
max_iterations,
);
}
persistent_fixpoint(transfer_body, current, next, changed, words, max_iterations)
}
#[cfg(test)]
mod tests {
use super::persistent_fixpoint_program;
use vyre_foundation::ir::{Expr, Node, Program};
use vyre_foundation::MemoryOrdering;
use vyre_primitives::fixpoint::persistent_fixpoint::{
persistent_fixpoint, PERSISTENT_FIXPOINT_WORKGROUP_SIZE,
};
fn required_workgroups(program: &Program) -> u32 {
let elements = program
.buffers()
.iter()
.map(|buffer| buffer.count())
.max()
.unwrap_or(1);
elements.div_ceil(program.workgroup_size()[0])
}
fn changed_words(program: &Program) -> u32 {
program
.buffers()
.iter()
.find(|buffer| buffer.name() == "changed")
.expect("Fix: persistent_fixpoint_program must declare its convergence-flag buffer.")
.count()
}
#[test]
fn builds_program_with_caller_buffers() {
let program = persistent_fixpoint_program(Vec::new(), "current", "next", "changed", 4, 8);
let names = program
.buffers()
.iter()
.map(|buffer| buffer.name())
.collect::<Vec<_>>();
assert!(names.contains(&"current"));
assert!(names.contains(&"next"));
assert!(names.contains(&"changed"));
}
#[test]
fn multi_workgroup_wrapper_never_shares_one_cleared_convergence_word() {
let program = persistent_fixpoint_program(Vec::new(), "current", "next", "changed", 257, 8);
assert_eq!(
required_workgroups(&program),
2,
"Fix: 257 words over a 256-wide workgroup must need two workgroups."
);
assert_eq!(
changed_words(&program),
8,
"Fix: a multi-workgroup fixpoint dispatch must use the per-iteration convergence-word protocol, not one shared cleared word."
);
}
fn count_grid_sync(nodes: &[Node]) -> usize {
nodes
.iter()
.map(|node| match node {
Node::Barrier {
ordering: MemoryOrdering::GridSync,
} => 1,
Node::If {
then, otherwise, ..
} => count_grid_sync(then) + count_grid_sync(otherwise),
Node::Loop { body, .. } | Node::Block(body) => count_grid_sync(body),
Node::Region { body, .. } => count_grid_sync(body),
_ => 0,
})
.sum()
}
fn publish_last_element_body(next: &str, last: u32) -> Vec<Node> {
vec![Node::if_then(
Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0)),
vec![Node::store(next, Expr::u32(last), Expr::u32(9))],
)]
}
fn run_fixpoint(
program: &Program,
reversed: bool,
words: u32,
changed_word_count: u32,
) -> (Vec<u32>, Vec<u32>) {
use vyre_reference::value::Value;
let to_value = |data: &[u32]| {
Value::Bytes(std::sync::Arc::from(vyre_primitives::wire::pack_u32_slice(
data,
)))
};
let zeros = vec![0_u32; words as usize];
let inputs = vec![
to_value(&zeros),
to_value(&zeros),
to_value(&vec![0_u32; changed_word_count as usize]),
];
let results = if reversed {
vyre_reference::reference_eval_lane_reversed(program, &inputs)
} else {
vyre_reference::reference_eval(program, &inputs)
}
.expect("Fix: the reference interpreter must execute the fixpoint program.");
let decode = |value: &vyre_reference::value::Value| -> Vec<u32> {
value
.to_bytes()
.chunks_exact(4)
.map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap()))
.collect()
};
(decode(&results[0]), decode(&results[2]))
}
#[test]
fn routing_threshold_is_the_declared_workgroup_width() {
let width = PERSISTENT_FIXPOINT_WORKGROUP_SIZE[0];
let at_width =
persistent_fixpoint_program(Vec::new(), "current", "next", "changed", width, 8);
assert_eq!(
at_width.workgroup_size(),
PERSISTENT_FIXPOINT_WORKGROUP_SIZE
);
assert_eq!(required_workgroups(&at_width), 1);
assert_eq!(
changed_words(&at_width),
1,
"Fix: a single-workgroup launch must keep the compact one-word convergence flag."
);
let past_width =
persistent_fixpoint_program(Vec::new(), "current", "next", "changed", width + 1, 8);
assert_eq!(required_workgroups(&past_width), 2);
assert_eq!(
changed_words(&past_width),
8,
"Fix: one word past the workgroup width already needs the per-iteration convergence words."
);
}
#[test]
fn grid_route_fences_the_grid_and_single_workgroup_route_does_not() {
let width = PERSISTENT_FIXPOINT_WORKGROUP_SIZE[0];
let single =
persistent_fixpoint_program(Vec::new(), "current", "next", "changed", width, 4);
assert_eq!(
count_grid_sync(single.entry()),
0,
"Fix: a single-workgroup fixpoint program must not force a cooperative grid launch."
);
let grid =
persistent_fixpoint_program(Vec::new(), "current", "next", "changed", width + 1, 4);
assert_eq!(
count_grid_sync(grid.entry()),
8,
"Fix: the grid form must fence each of its 4 waves twice, once after the transfer step and once after the compare."
);
}
#[test]
fn grid_route_sizes_changed_to_one_word_per_iteration() {
let width = PERSISTENT_FIXPOINT_WORKGROUP_SIZE[0];
for max_iterations in [1_u32, 2, 8, 64] {
let program = persistent_fixpoint_program(
Vec::new(),
"current",
"next",
"changed",
width + 1,
max_iterations,
);
assert_eq!(
changed_words(&program),
max_iterations,
"Fix: the grid route needs one convergence word per iteration; {max_iterations} iterations need {max_iterations} words."
);
}
}
#[test]
fn single_word_harness_returns_wrong_state_above_one_workgroup() {
let words = PERSISTENT_FIXPOINT_WORKGROUP_SIZE[0] + 1;
let last = words - 1;
let max_iterations = 4_u32;
let unsound = persistent_fixpoint(
publish_last_element_body("next", last),
"current",
"next",
"changed",
words,
max_iterations,
);
assert_eq!(
changed_words(&unsound),
1,
"Fix: this fixture must exercise the single shared convergence word."
);
let (forward, forward_flag) = run_fixpoint(&unsound, false, words, 1);
let (reversed, reversed_flag) = run_fixpoint(&unsound, true, words, 1);
assert_eq!(
forward[last as usize], 9,
"Fix: stepping group 0 first must reach the fixpoint, proving the divergence is cross-workgroup ordering."
);
assert_eq!(
forward_flag[0], 1,
"Fix: the correct schedule must leave the flag set, since group 1 sets it and nobody clears it afterwards."
);
assert_eq!(
reversed[last as usize],
0,
"Fix: this test records the OBSERVED wrong value the racing shared flag produces; if the single-word harness stops diverging here, re-derive the defect before deleting this test."
);
assert_eq!(
reversed_flag[0], 0,
"Fix: the shared flag must be observed claiming convergence while the last element is unpublished, which is what makes the wrong answer silent."
);
}
#[test]
fn grid_routed_wrapper_is_order_independent_where_single_word_diverges() {
let words = PERSISTENT_FIXPOINT_WORKGROUP_SIZE[0] + 1;
let last = words - 1;
let max_iterations = 4_u32;
let routed = persistent_fixpoint_program(
publish_last_element_body("next", last),
"current",
"next",
"changed",
words,
max_iterations,
);
assert_eq!(
changed_words(&routed),
max_iterations,
"Fix: this size must route to the grid harness."
);
for reversed in [false, true] {
let (current, _) = run_fixpoint(&routed, reversed, words, max_iterations);
assert_eq!(
current[last as usize], 9,
"Fix: the grid-routed program must reach the fixpoint in both workgroup orders (reversed={reversed})."
);
}
}
}