#![allow(clippy::too_many_arguments)]
pub(crate) struct FixedPointClosureLabels {
pub(crate) require_kind: &'static str,
pub(crate) iterations: &'static str,
pub(crate) word_capacity: &'static str,
pub(crate) bitset_words: &'static str,
pub(crate) seed_tail: &'static str,
pub(crate) unpack_outputs: &'static str,
pub(crate) frontier_output: &'static str,
pub(crate) output_tail: &'static str,
pub(crate) convergence: &'static str,
}
#[allow(dead_code)]
pub(crate) fn fallback_bitset_equal_dispatch(
inputs: &[&[u8]],
outputs: &mut Vec<Vec<u8>>,
) -> Result<(), String> {
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
let eq = inputs.len() >= 2 && inputs[0] == inputs[1];
outputs[0].extend_from_slice(&if eq { 1u32 } else { 0u32 }.to_le_bytes());
Ok(())
}
pub(crate) fn run_forward_bitset_closure_into<F>(
dispatch: &F,
graph: &crate::fixed_point_graph::FixedPointForwardGraph,
expected_kind: crate::fixed_point_graph::FixedPointAnalysisKind,
program: &vyre::ir::Program,
seed_bits: &[u32],
max_iterations: u32,
scratch: &mut crate::fixed_point_scratch::FixedPointScratch,
result: &mut Vec<u32>,
labels: FixedPointClosureLabels,
) -> Result<(), String>
where
F: Fn(&vyre::ir::Program, &[&[u8]], Option<[u32; 3]>, &mut Vec<Vec<u8>>) -> Result<(), String>,
{
graph.require_kind(expected_kind, labels.require_kind)?;
crate::dispatch_decode::require_positive_iterations(labels.iterations, max_iterations)?;
let node_count = graph.node_count();
let words = crate::dispatch_decode::bitset_word_capacity(labels.word_capacity, node_count)?;
crate::dispatch_decode::require_bitset_words(labels.bitset_words, seed_bits, words)?;
crate::dispatch_decode::require_bitset_tail_clear(labels.seed_tail, seed_bits, node_count)?;
scratch.prepare_frontier_words(words, seed_bits)?;
scratch.begin_frontier_density(node_count);
scratch.prepare_next_words(words)?;
for _ in 0..max_iterations {
crate::dispatch_decode::try_pack_u32_into(&scratch.frontier_words, &mut scratch.frontier_bytes, "weir u32 byte staging")?;
let inputs = [
graph.pg_nodes_bytes.as_slice(),
graph.edge_offsets_bytes.as_slice(),
graph.edge_targets_bytes.as_slice(),
graph.edge_kind_mask_bytes.as_slice(),
graph.pg_node_tags_bytes.as_slice(),
scratch.frontier_bytes.as_slice(),
scratch.frontier_bytes.as_slice(),
];
dispatch(
program,
&inputs,
Some([node_count.max(1), 1, 1]),
&mut scratch.outputs,
)?;
crate::dispatch_decode::unpack_only_exact_u32_into(
&scratch.outputs,
labels.unpack_outputs,
labels.frontier_output,
words,
&mut scratch.next,
)?;
crate::dispatch_decode::require_bitset_tail_clear(
labels.output_tail,
&scratch.next,
node_count,
)?;
scratch.record_decoded_frontier_transition();
match crate::fixed_point_scratch::converged_via_gpu_into(
&scratch.outputs[0],
&scratch.frontier_bytes,
words,
|program, inputs, grid, out| dispatch(program, inputs, grid, out),
) {
Ok(true) => {
crate::fixed_point_scratch::copy_words_into(&scratch.next, result)?;
return Ok(());
}
Ok(false) => {}
Err(_) => {
if scratch.next == scratch.frontier_words {
crate::fixed_point_scratch::copy_words_into(&scratch.next, result)?;
return Ok(());
}
}
}
std::mem::swap(&mut scratch.frontier_words, &mut scratch.next);
}
Err(format!(
"{} did not converge within {max_iterations} iterations",
labels.convergence
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fixed_point_graph::{FixedPointAnalysisKind, FixedPointForwardGraph};
use crate::fixed_point_scratch::FixedPointScratch;
fn dummy_program() -> vyre::ir::Program {
vyre::ir::Program::default()
}
fn test_labels() -> FixedPointClosureLabels {
FixedPointClosureLabels {
require_kind: "test-require-kind",
iterations: "test-iterations",
word_capacity: "test-word-capacity",
bitset_words: "test-bitset-words",
seed_tail: "test-seed-tail",
unpack_outputs: "test-unpack-outputs",
frontier_output: "test-frontier-output",
output_tail: "test-output-tail",
convergence: "test-convergence",
}
}
fn make_graph(node_count: u32, edges: &[(u32, u32)]) -> FixedPointForwardGraph {
let mut adj: Vec<Vec<u32>> = vec![Vec::new(); node_count as usize];
for &(src, dst) in edges {
adj[src as usize].push(dst);
}
let mut edge_offsets = vec![0u32; node_count as usize + 1];
let mut edge_targets = Vec::new();
let mut edge_kind_mask = Vec::new();
for i in 0..node_count as usize {
edge_targets.extend(&adj[i]);
edge_kind_mask.extend(std::iter::repeat_n(1u32, adj[i].len()));
edge_offsets[i + 1] = edge_targets.len() as u32;
}
FixedPointForwardGraph::new(
"test",
node_count,
&edge_offsets,
&edge_targets,
&edge_kind_mask,
)
.expect("valid graph")
}
fn zero_seed(node_count: u32) -> Vec<u32> {
let words = crate::dispatch_decode::bitset_words(node_count) as usize;
vec![0u32; words]
}
fn full_seed(node_count: u32) -> Vec<u32> {
if node_count == 0 {
return Vec::new();
}
let words = crate::dispatch_decode::bitset_words(node_count) as usize;
let mut seed = vec![u32::MAX; words];
let tail = node_count % 32;
if tail != 0 {
seed[words - 1] = (1u32 << tail) - 1;
}
seed
}
fn dirty_seed(node_count: u32) -> Vec<u32> {
let mut seed = zero_seed(node_count);
if !seed.is_empty() {
let last = seed.len() - 1;
seed[last] = u32::MAX;
} else if node_count == 0 {
seed.push(u32::MAX);
}
seed
}
fn pack_words(words: &[u32]) -> Vec<u8> {
words.iter().flat_map(|&w| w.to_le_bytes()).collect()
}
fn unpack_words(bytes: &[u8]) -> Vec<u32> {
bytes
.chunks_exact(4)
.map(|b| u32::from_le_bytes(b.try_into().unwrap()))
.collect()
}
fn is_bitset_equal_program(program: &vyre::ir::Program) -> bool {
vyre_primitives::bitset::equal::is_bitset_equal_program(program)
}
fn handle_bitset_equal_dispatch(
inputs: &[&[u8]],
outputs: &mut Vec<Vec<u8>>,
) -> Result<(), String> {
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
let eq = inputs.len() >= 2 && inputs[0] == inputs[1];
outputs[0].extend_from_slice(&if eq { 1u32 } else { 0u32 }.to_le_bytes());
Ok(())
}
fn identity_dispatch(
program: &vyre::ir::Program,
inputs: &[&[u8]],
_grid: Option<[u32; 3]>,
outputs: &mut Vec<Vec<u8>>,
) -> Result<(), String> {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(inputs, outputs);
}
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
outputs[0].extend_from_slice(inputs[5]);
Ok(())
}
fn twice_then_identity_dispatch(
program: &vyre::ir::Program,
inputs: &[&[u8]],
_grid: Option<[u32; 3]>,
outputs: &mut Vec<Vec<u8>>,
) -> Result<(), String> {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(inputs, outputs);
}
use std::cell::Cell;
thread_local! { static COUNTER: Cell<u32> = const { Cell::new(0) }; }
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
let count = COUNTER.get();
if count < 2 {
let mut words = unpack_words(inputs[5]);
if !words.is_empty() {
words[0] |= 1;
}
outputs[0].extend_from_slice(&pack_words(&words));
} else {
outputs[0].extend_from_slice(inputs[5]);
}
COUNTER.set(count + 1);
Ok(())
}
fn reset_twice_counter() {
use std::cell::Cell;
thread_local! { static COUNTER: Cell<u32> = const { Cell::new(0) }; }
COUNTER.set(0);
}
fn counting_dispatch(
program: &vyre::ir::Program,
inputs: &[&[u8]],
_grid: Option<[u32; 3]>,
outputs: &mut Vec<Vec<u8>>,
) -> Result<(), String> {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(inputs, outputs);
}
use std::cell::Cell;
thread_local! { static COUNTER: Cell<u32> = const { Cell::new(0) }; }
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
let count = COUNTER.get();
let words = inputs[5].len() / 4;
let mut words_vec = vec![0u32; words];
if !words_vec.is_empty() {
words_vec[0] = if count % 2 == 0 { 1 } else { 0 };
}
outputs[0].extend_from_slice(&pack_words(&words_vec));
COUNTER.set(count + 1);
Ok(())
}
fn reset_counting_counter() {
use std::cell::Cell;
thread_local! { static COUNTER: Cell<u32> = const { Cell::new(0) }; }
COUNTER.set(0);
}
fn error_dispatch(
msg: &'static str,
) -> impl Fn(&vyre::ir::Program, &[&[u8]], Option<[u32; 3]>, &mut Vec<Vec<u8>>) -> Result<(), String>
{
move |_program, _inputs, _grid, _outputs| Err(msg.to_string())
}
fn bad_output_count_dispatch(
program: &vyre::ir::Program,
_inputs: &[&[u8]],
_grid: Option<[u32; 3]>,
outputs: &mut Vec<Vec<u8>>,
) -> Result<(), String> {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(_inputs, outputs);
}
outputs.push(vec![0; 4]);
outputs.push(vec![0; 4]);
Ok(())
}
fn bad_byte_len_dispatch(
program: &vyre::ir::Program,
_inputs: &[&[u8]],
_grid: Option<[u32; 3]>,
outputs: &mut Vec<Vec<u8>>,
) -> Result<(), String> {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(_inputs, outputs);
}
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
outputs[0].extend_from_slice(&[0u8; 3]);
Ok(())
}
fn dirty_output_tail_dispatch(
program: &vyre::ir::Program,
inputs: &[&[u8]],
_grid: Option<[u32; 3]>,
outputs: &mut Vec<Vec<u8>>,
) -> Result<(), String> {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(inputs, outputs);
}
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
outputs[0].extend_from_slice(inputs[5]);
if let Some(last) = outputs[0].last_mut() {
*last |= 0x80;
}
Ok(())
}
macro_rules! identity_converges {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert_eq!(result, seed);
}
};
}
macro_rules! full_seed_converges {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = full_seed($node_count);
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert_eq!(result, seed);
}
};
}
macro_rules! dispatch_called_exactly_once {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
use std::cell::Cell;
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
let calls = Cell::new(0);
let counting = |program: &vyre::ir::Program,
inputs: &[&[u8]],
_grid: Option<[u32; 3]>,
outputs: &mut Vec<Vec<u8>>| {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(inputs, outputs);
}
calls.set(calls.get() + 1);
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
outputs[0].extend_from_slice(inputs[5]);
Ok(())
};
run_forward_bitset_closure_into(
&counting,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert_eq!(
calls.get(),
1,
"identity dispatch must be called exactly once"
);
}
};
}
macro_rules! two_iteration_convergence {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
reset_twice_counter();
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
run_forward_bitset_closure_into(
&twice_then_identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert_eq!(scratch.frontier_words, scratch.next);
}
};
}
macro_rules! non_convergence_fails {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
if $node_count == 0 {
return;
}
reset_counting_counter();
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
let err = run_forward_bitset_closure_into(
&counting_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
3,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must fail when not converging");
assert!(
err.contains("did not converge within 3 iterations"),
"unexpected error: {err}"
);
}
};
}
macro_rules! zero_max_iterations_fails {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
let err = run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
0,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must fail with zero iterations");
assert!(err.contains("max_iterations=0"), "unexpected error: {err}");
}
};
}
macro_rules! wrong_seed_length_fails {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = if $node_count == 0 {
vec![1u32]
} else {
vec![0u32; zero_seed($node_count).len() + 1]
};
let err = run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must fail with wrong seed length");
assert!(
err.contains("bitset has") && err.contains("expected exactly"),
"unexpected error: {err}"
);
}
};
}
macro_rules! wrong_kind_fails {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
let err = run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Reaching,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must fail with wrong kind");
assert!(
err.contains("expected Reaching") || err.contains("received a Generic"),
"unexpected error: {err}"
);
}
};
}
macro_rules! dispatch_error_propagates {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
let err = run_forward_bitset_closure_into(
&error_dispatch("gpu explosion"),
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must propagate dispatch error");
assert!(err.contains("gpu explosion"), "unexpected error: {err}");
}
};
}
macro_rules! bad_output_count_fails {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
let err = run_forward_bitset_closure_into(
&bad_output_count_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must fail with bad output count");
assert!(
err.contains("returned 2 output buffers"),
"unexpected error: {err}"
);
}
};
}
macro_rules! bad_byte_length_fails {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
let err = run_forward_bitset_closure_into(
&bad_byte_len_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must fail with bad byte length");
assert!(
err.contains("malformed byte length"),
"unexpected error: {err}"
);
}
};
}
macro_rules! dirty_seed_tail_fails {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
if $node_count == 0 || ($node_count > 0 && $node_count % 32 == 0) {
return;
}
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = dirty_seed($node_count);
let err = run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must fail with dirty seed tail");
assert!(
err.contains("outside the declared domain"),
"unexpected error: {err}"
);
}
};
}
macro_rules! dirty_output_tail_fails {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
if $node_count == 0 || ($node_count > 0 && $node_count % 32 == 0) {
return;
}
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
let err = run_forward_bitset_closure_into(
&dirty_output_tail_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must fail with dirty output tail");
assert!(
err.contains("outside the declared domain"),
"unexpected error: {err}"
);
}
};
}
macro_rules! borrowed_owned_parity {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch1 = FixedPointScratch::default();
let mut result1 = Vec::new();
let mut scratch2 = FixedPointScratch::default();
let mut result2 = Vec::new();
let seed = full_seed($node_count);
let owned = |program: &vyre::ir::Program,
inputs: &[&[u8]],
_grid: Option<[u32; 3]>,
outputs: &mut Vec<Vec<u8>>| {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(inputs, outputs);
}
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
outputs[0].extend_from_slice(inputs[5]);
Ok(())
};
run_forward_bitset_closure_into(
&owned,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch1,
&mut result1,
test_labels(),
)
.expect("owned closure must converge");
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch2,
&mut result2,
test_labels(),
)
.expect("borrowed fn must converge");
assert_eq!(result1, result2);
}
};
}
macro_rules! result_buffer_reused {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let seed = full_seed($node_count);
let mut result = seed.clone();
let old_ptr = result.as_ptr();
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert_eq!(result, seed);
if result.len() == seed.len() {
assert_eq!(
result.as_ptr(),
old_ptr,
"result buffer should be reused when lengths match"
);
}
}
};
}
macro_rules! scratch_swap_tracks_iterations {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
reset_twice_counter();
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
run_forward_bitset_closure_into(
&twice_then_identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert_eq!(scratch.frontier_words, result);
assert_eq!(scratch.next, result);
}
};
}
macro_rules! empty_frontier_converges_in_one {
($name:ident, $node_count:expr) => {
#[test]
fn $name() {
let graph = make_graph($node_count, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed($node_count);
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
1,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge in exactly one iteration even with budget 1");
assert_eq!(result, seed);
}
};
}
identity_converges!(c1_identity_0, 0);
identity_converges!(c1_identity_1, 1);
identity_converges!(c1_identity_2, 2);
identity_converges!(c1_identity_3, 3);
identity_converges!(c1_identity_5, 5);
identity_converges!(c1_identity_10, 10);
identity_converges!(c1_identity_31, 31);
identity_converges!(c1_identity_32, 32);
identity_converges!(c1_identity_33, 33);
identity_converges!(c1_identity_63, 63);
identity_converges!(c1_identity_64, 64);
identity_converges!(c1_identity_65, 65);
identity_converges!(c1_identity_100, 100);
identity_converges!(c1_identity_1k, 1000);
identity_converges!(c1_identity_10k, 10000);
identity_converges!(c1_identity_100k, 100000);
full_seed_converges!(c1_full_seed_0, 0);
full_seed_converges!(c1_full_seed_1, 1);
full_seed_converges!(c1_full_seed_2, 2);
full_seed_converges!(c1_full_seed_3, 3);
full_seed_converges!(c1_full_seed_5, 5);
full_seed_converges!(c1_full_seed_10, 10);
full_seed_converges!(c1_full_seed_31, 31);
full_seed_converges!(c1_full_seed_32, 32);
full_seed_converges!(c1_full_seed_33, 33);
full_seed_converges!(c1_full_seed_63, 63);
full_seed_converges!(c1_full_seed_64, 64);
full_seed_converges!(c1_full_seed_65, 65);
full_seed_converges!(c1_full_seed_100, 100);
full_seed_converges!(c1_full_seed_1k, 1000);
full_seed_converges!(c1_full_seed_10k, 10000);
full_seed_converges!(c1_full_seed_100k, 100000);
dispatch_called_exactly_once!(c2_early_exit_0, 0);
dispatch_called_exactly_once!(c2_early_exit_1, 1);
dispatch_called_exactly_once!(c2_early_exit_2, 2);
dispatch_called_exactly_once!(c2_early_exit_3, 3);
dispatch_called_exactly_once!(c2_early_exit_5, 5);
dispatch_called_exactly_once!(c2_early_exit_10, 10);
dispatch_called_exactly_once!(c2_early_exit_31, 31);
dispatch_called_exactly_once!(c2_early_exit_32, 32);
dispatch_called_exactly_once!(c2_early_exit_33, 33);
dispatch_called_exactly_once!(c2_early_exit_63, 63);
dispatch_called_exactly_once!(c2_early_exit_64, 64);
dispatch_called_exactly_once!(c2_early_exit_65, 65);
dispatch_called_exactly_once!(c2_early_exit_100, 100);
dispatch_called_exactly_once!(c2_early_exit_1k, 1000);
dispatch_called_exactly_once!(c2_early_exit_10k, 10000);
dispatch_called_exactly_once!(c2_early_exit_100k, 100000);
two_iteration_convergence!(c3_two_iter_0, 0);
two_iteration_convergence!(c3_two_iter_1, 1);
two_iteration_convergence!(c3_two_iter_2, 2);
two_iteration_convergence!(c3_two_iter_3, 3);
two_iteration_convergence!(c3_two_iter_5, 5);
two_iteration_convergence!(c3_two_iter_10, 10);
two_iteration_convergence!(c3_two_iter_31, 31);
two_iteration_convergence!(c3_two_iter_32, 32);
two_iteration_convergence!(c3_two_iter_33, 33);
two_iteration_convergence!(c3_two_iter_63, 63);
two_iteration_convergence!(c3_two_iter_64, 64);
two_iteration_convergence!(c3_two_iter_65, 65);
two_iteration_convergence!(c3_two_iter_100, 100);
two_iteration_convergence!(c3_two_iter_1k, 1000);
two_iteration_convergence!(c3_two_iter_10k, 10000);
two_iteration_convergence!(c3_two_iter_100k, 100000);
non_convergence_fails!(c4_non_converge_0, 0);
non_convergence_fails!(c4_non_converge_1, 1);
non_convergence_fails!(c4_non_converge_2, 2);
non_convergence_fails!(c4_non_converge_3, 3);
non_convergence_fails!(c4_non_converge_5, 5);
non_convergence_fails!(c4_non_converge_10, 10);
non_convergence_fails!(c4_non_converge_31, 31);
non_convergence_fails!(c4_non_converge_32, 32);
non_convergence_fails!(c4_non_converge_33, 33);
non_convergence_fails!(c4_non_converge_63, 63);
non_convergence_fails!(c4_non_converge_64, 64);
non_convergence_fails!(c4_non_converge_65, 65);
non_convergence_fails!(c4_non_converge_100, 100);
non_convergence_fails!(c4_non_converge_1k, 1000);
non_convergence_fails!(c4_non_converge_10k, 10000);
non_convergence_fails!(c4_non_converge_100k, 100000);
zero_max_iterations_fails!(c4_zero_max_0, 0);
zero_max_iterations_fails!(c4_zero_max_1, 1);
zero_max_iterations_fails!(c4_zero_max_2, 2);
zero_max_iterations_fails!(c4_zero_max_3, 3);
zero_max_iterations_fails!(c4_zero_max_5, 5);
zero_max_iterations_fails!(c4_zero_max_10, 10);
zero_max_iterations_fails!(c4_zero_max_31, 31);
zero_max_iterations_fails!(c4_zero_max_32, 32);
zero_max_iterations_fails!(c4_zero_max_33, 33);
zero_max_iterations_fails!(c4_zero_max_63, 63);
zero_max_iterations_fails!(c4_zero_max_64, 64);
zero_max_iterations_fails!(c4_zero_max_65, 65);
zero_max_iterations_fails!(c4_zero_max_100, 100);
zero_max_iterations_fails!(c4_zero_max_1k, 1000);
zero_max_iterations_fails!(c4_zero_max_10k, 10000);
zero_max_iterations_fails!(c4_zero_max_100k, 100000);
wrong_seed_length_fails!(c10_seed_len_0, 0);
wrong_seed_length_fails!(c10_seed_len_1, 1);
wrong_seed_length_fails!(c10_seed_len_2, 2);
wrong_seed_length_fails!(c10_seed_len_3, 3);
wrong_seed_length_fails!(c10_seed_len_5, 5);
wrong_seed_length_fails!(c10_seed_len_10, 10);
wrong_seed_length_fails!(c10_seed_len_31, 31);
wrong_seed_length_fails!(c10_seed_len_32, 32);
wrong_seed_length_fails!(c10_seed_len_33, 33);
wrong_seed_length_fails!(c10_seed_len_63, 63);
wrong_seed_length_fails!(c10_seed_len_64, 64);
wrong_seed_length_fails!(c10_seed_len_65, 65);
wrong_seed_length_fails!(c10_seed_len_100, 100);
wrong_seed_length_fails!(c10_seed_len_1k, 1000);
wrong_seed_length_fails!(c10_seed_len_10k, 10000);
wrong_seed_length_fails!(c10_seed_len_100k, 100000);
wrong_kind_fails!(c10_kind_0, 0);
wrong_kind_fails!(c10_kind_1, 1);
wrong_kind_fails!(c10_kind_2, 2);
wrong_kind_fails!(c10_kind_3, 3);
wrong_kind_fails!(c10_kind_5, 5);
wrong_kind_fails!(c10_kind_10, 10);
wrong_kind_fails!(c10_kind_31, 31);
wrong_kind_fails!(c10_kind_32, 32);
wrong_kind_fails!(c10_kind_33, 33);
wrong_kind_fails!(c10_kind_63, 63);
wrong_kind_fails!(c10_kind_64, 64);
wrong_kind_fails!(c10_kind_65, 65);
wrong_kind_fails!(c10_kind_100, 100);
wrong_kind_fails!(c10_kind_1k, 1000);
wrong_kind_fails!(c10_kind_10k, 10000);
wrong_kind_fails!(c10_kind_100k, 100000);
dispatch_error_propagates!(c10_dispatch_err_0, 0);
dispatch_error_propagates!(c10_dispatch_err_1, 1);
dispatch_error_propagates!(c10_dispatch_err_2, 2);
dispatch_error_propagates!(c10_dispatch_err_3, 3);
dispatch_error_propagates!(c10_dispatch_err_5, 5);
dispatch_error_propagates!(c10_dispatch_err_10, 10);
dispatch_error_propagates!(c10_dispatch_err_31, 31);
dispatch_error_propagates!(c10_dispatch_err_32, 32);
dispatch_error_propagates!(c10_dispatch_err_33, 33);
dispatch_error_propagates!(c10_dispatch_err_63, 63);
dispatch_error_propagates!(c10_dispatch_err_64, 64);
dispatch_error_propagates!(c10_dispatch_err_65, 65);
dispatch_error_propagates!(c10_dispatch_err_100, 100);
dispatch_error_propagates!(c10_dispatch_err_1k, 1000);
dispatch_error_propagates!(c10_dispatch_err_10k, 10000);
dispatch_error_propagates!(c10_dispatch_err_100k, 100000);
bad_output_count_fails!(c10_out_count_0, 0);
bad_output_count_fails!(c10_out_count_1, 1);
bad_output_count_fails!(c10_out_count_2, 2);
bad_output_count_fails!(c10_out_count_3, 3);
bad_output_count_fails!(c10_out_count_5, 5);
bad_output_count_fails!(c10_out_count_10, 10);
bad_output_count_fails!(c10_out_count_31, 31);
bad_output_count_fails!(c10_out_count_32, 32);
bad_output_count_fails!(c10_out_count_33, 33);
bad_output_count_fails!(c10_out_count_63, 63);
bad_output_count_fails!(c10_out_count_64, 64);
bad_output_count_fails!(c10_out_count_65, 65);
bad_output_count_fails!(c10_out_count_100, 100);
bad_output_count_fails!(c10_out_count_1k, 1000);
bad_output_count_fails!(c10_out_count_10k, 10000);
bad_output_count_fails!(c10_out_count_100k, 100000);
bad_byte_length_fails!(c10_byte_len_0, 0);
bad_byte_length_fails!(c10_byte_len_1, 1);
bad_byte_length_fails!(c10_byte_len_2, 2);
bad_byte_length_fails!(c10_byte_len_3, 3);
bad_byte_length_fails!(c10_byte_len_5, 5);
bad_byte_length_fails!(c10_byte_len_10, 10);
bad_byte_length_fails!(c10_byte_len_31, 31);
bad_byte_length_fails!(c10_byte_len_32, 32);
bad_byte_length_fails!(c10_byte_len_33, 33);
bad_byte_length_fails!(c10_byte_len_63, 63);
bad_byte_length_fails!(c10_byte_len_64, 64);
bad_byte_length_fails!(c10_byte_len_65, 65);
bad_byte_length_fails!(c10_byte_len_100, 100);
bad_byte_length_fails!(c10_byte_len_1k, 1000);
bad_byte_length_fails!(c10_byte_len_10k, 10000);
bad_byte_length_fails!(c10_byte_len_100k, 100000);
dirty_seed_tail_fails!(c5_seed_tail_0, 0);
dirty_seed_tail_fails!(c5_seed_tail_1, 1);
dirty_seed_tail_fails!(c5_seed_tail_2, 2);
dirty_seed_tail_fails!(c5_seed_tail_3, 3);
dirty_seed_tail_fails!(c5_seed_tail_5, 5);
dirty_seed_tail_fails!(c5_seed_tail_10, 10);
dirty_seed_tail_fails!(c5_seed_tail_31, 31);
dirty_seed_tail_fails!(c5_seed_tail_32, 32);
dirty_seed_tail_fails!(c5_seed_tail_33, 33);
dirty_seed_tail_fails!(c5_seed_tail_63, 63);
dirty_seed_tail_fails!(c5_seed_tail_64, 64);
dirty_seed_tail_fails!(c5_seed_tail_65, 65);
dirty_seed_tail_fails!(c5_seed_tail_100, 100);
dirty_seed_tail_fails!(c5_seed_tail_1k, 1000);
dirty_seed_tail_fails!(c5_seed_tail_10k, 10000);
dirty_seed_tail_fails!(c5_seed_tail_100k, 100000);
dirty_output_tail_fails!(c5_out_tail_0, 0);
dirty_output_tail_fails!(c5_out_tail_1, 1);
dirty_output_tail_fails!(c5_out_tail_2, 2);
dirty_output_tail_fails!(c5_out_tail_3, 3);
dirty_output_tail_fails!(c5_out_tail_5, 5);
dirty_output_tail_fails!(c5_out_tail_10, 10);
dirty_output_tail_fails!(c5_out_tail_31, 31);
dirty_output_tail_fails!(c5_out_tail_32, 32);
dirty_output_tail_fails!(c5_out_tail_33, 33);
dirty_output_tail_fails!(c5_out_tail_63, 63);
dirty_output_tail_fails!(c5_out_tail_64, 64);
dirty_output_tail_fails!(c5_out_tail_65, 65);
dirty_output_tail_fails!(c5_out_tail_100, 100);
dirty_output_tail_fails!(c5_out_tail_1k, 1000);
dirty_output_tail_fails!(c5_out_tail_10k, 10000);
dirty_output_tail_fails!(c5_out_tail_100k, 100000);
borrowed_owned_parity!(c6_parity_0, 0);
borrowed_owned_parity!(c6_parity_1, 1);
borrowed_owned_parity!(c6_parity_2, 2);
borrowed_owned_parity!(c6_parity_3, 3);
borrowed_owned_parity!(c6_parity_5, 5);
borrowed_owned_parity!(c6_parity_10, 10);
borrowed_owned_parity!(c6_parity_31, 31);
borrowed_owned_parity!(c6_parity_32, 32);
borrowed_owned_parity!(c6_parity_33, 33);
borrowed_owned_parity!(c6_parity_63, 63);
borrowed_owned_parity!(c6_parity_64, 64);
borrowed_owned_parity!(c6_parity_65, 65);
borrowed_owned_parity!(c6_parity_100, 100);
borrowed_owned_parity!(c6_parity_1k, 1000);
borrowed_owned_parity!(c6_parity_10k, 10000);
borrowed_owned_parity!(c6_parity_100k, 100000);
result_buffer_reused!(c3_result_reuse_0, 0);
result_buffer_reused!(c3_result_reuse_1, 1);
result_buffer_reused!(c3_result_reuse_2, 2);
result_buffer_reused!(c3_result_reuse_3, 3);
result_buffer_reused!(c3_result_reuse_5, 5);
result_buffer_reused!(c3_result_reuse_10, 10);
result_buffer_reused!(c3_result_reuse_31, 31);
result_buffer_reused!(c3_result_reuse_32, 32);
result_buffer_reused!(c3_result_reuse_33, 33);
result_buffer_reused!(c3_result_reuse_63, 63);
result_buffer_reused!(c3_result_reuse_64, 64);
result_buffer_reused!(c3_result_reuse_65, 65);
result_buffer_reused!(c3_result_reuse_100, 100);
result_buffer_reused!(c3_result_reuse_1k, 1000);
result_buffer_reused!(c3_result_reuse_10k, 10000);
result_buffer_reused!(c3_result_reuse_100k, 100000);
scratch_swap_tracks_iterations!(c3_scratch_swap_0, 0);
scratch_swap_tracks_iterations!(c3_scratch_swap_1, 1);
scratch_swap_tracks_iterations!(c3_scratch_swap_2, 2);
scratch_swap_tracks_iterations!(c3_scratch_swap_3, 3);
scratch_swap_tracks_iterations!(c3_scratch_swap_5, 5);
scratch_swap_tracks_iterations!(c3_scratch_swap_10, 10);
scratch_swap_tracks_iterations!(c3_scratch_swap_31, 31);
scratch_swap_tracks_iterations!(c3_scratch_swap_32, 32);
scratch_swap_tracks_iterations!(c3_scratch_swap_33, 33);
scratch_swap_tracks_iterations!(c3_scratch_swap_63, 63);
scratch_swap_tracks_iterations!(c3_scratch_swap_64, 64);
scratch_swap_tracks_iterations!(c3_scratch_swap_65, 65);
scratch_swap_tracks_iterations!(c3_scratch_swap_100, 100);
scratch_swap_tracks_iterations!(c3_scratch_swap_1k, 1000);
scratch_swap_tracks_iterations!(c3_scratch_swap_10k, 10000);
scratch_swap_tracks_iterations!(c3_scratch_swap_100k, 100000);
empty_frontier_converges_in_one!(c7_empty_0, 0);
empty_frontier_converges_in_one!(c7_empty_1, 1);
empty_frontier_converges_in_one!(c7_empty_2, 2);
empty_frontier_converges_in_one!(c7_empty_3, 3);
empty_frontier_converges_in_one!(c7_empty_5, 5);
empty_frontier_converges_in_one!(c7_empty_10, 10);
empty_frontier_converges_in_one!(c7_empty_31, 31);
empty_frontier_converges_in_one!(c7_empty_32, 32);
empty_frontier_converges_in_one!(c7_empty_33, 33);
empty_frontier_converges_in_one!(c7_empty_63, 63);
empty_frontier_converges_in_one!(c7_empty_64, 64);
empty_frontier_converges_in_one!(c7_empty_65, 65);
empty_frontier_converges_in_one!(c7_empty_100, 100);
empty_frontier_converges_in_one!(c7_empty_1k, 1000);
empty_frontier_converges_in_one!(c7_empty_10k, 10000);
empty_frontier_converges_in_one!(c7_empty_100k, 100000);
#[test]
fn c8_single_node_no_self_loop_converges_immediately() {
let graph = make_graph(1, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = vec![1u32];
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert_eq!(result, seed);
}
#[test]
fn c8_single_node_with_self_loop_two_iterations() {
let graph = make_graph(1, &[(0, 0)]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = vec![1u32];
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge even with self-loop");
assert_eq!(result, seed);
}
#[test]
fn c7_empty_graph_zero_budget_fails() {
let graph = make_graph(0, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let err = run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&[],
0,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must fail");
assert!(err.contains("max_iterations=0"));
}
#[test]
fn c7_empty_graph_converges_with_budget_one() {
let graph = make_graph(0, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&[],
1,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert!(result.is_empty());
}
#[test]
fn c3_result_grows_when_undersized() {
let graph = make_graph(5, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = vec![99u32];
let seed = full_seed(5);
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert_eq!(result, seed);
}
#[test]
fn c3_result_shrinks_when_oversized() {
let graph = make_graph(5, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = vec![99u32; 10];
let seed = full_seed(5);
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert_eq!(result, seed);
}
#[test]
fn c1_frontier_telemetry_recorded_after_convergence() {
let graph = make_graph(10, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = vec![0b1010u32];
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
let telemetry = scratch.frontier_density();
assert_eq!(telemetry.iterations, 1);
assert_eq!(telemetry.samples, 2);
}
#[test]
fn c4_convergence_with_max_iterations_equal_to_actual() {
reset_twice_counter();
let graph = make_graph(4, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed(4);
run_forward_bitset_closure_into(
&twice_then_identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
3,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge when budget exactly matches required iterations");
}
#[test]
fn c4_non_convergence_when_budget_one_short() {
reset_twice_counter();
let graph = make_graph(4, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed(4);
let err = run_forward_bitset_closure_into(
&twice_then_identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
1,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must fail when budget is one short");
assert!(err.contains("did not converge within 1 iterations"));
}
#[test]
fn c5_tail_clear_rejects_single_bit_past_domain_31() {
let graph = make_graph(31, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = vec![0xFFFFFFFFu32];
let err = run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must reject bit past domain");
assert!(err.contains("outside the declared domain"));
}
#[test]
fn c5_tail_clear_accepts_exact_boundary_32() {
let graph = make_graph(32, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = vec![0xFFFFFFFFu32];
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("32-bit exact boundary is valid");
assert_eq!(result, seed);
}
#[test]
fn c5_tail_clear_rejects_bit_33_for_33_node_domain() {
let graph = make_graph(33, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = vec![0xFFFFFFFFu32, 0x00000002u32];
let err = run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must reject bit 33 for 33-node domain");
assert!(err.contains("outside the declared domain"));
}
#[test]
fn c6_borrowed_closure_inline_parity() {
let graph = make_graph(7, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = vec![0b1010101u32];
run_forward_bitset_closure_into(
&|program, inputs, _g, outputs| {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(inputs, outputs);
}
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
outputs[0].extend_from_slice(inputs[5]);
Ok(())
},
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("inline borrowed closure must converge");
assert_eq!(result, seed);
}
#[test]
fn c6_mut_borrowed_closure_parity() {
let graph = make_graph(7, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = vec![0b1010101u32];
let mut closure = |program: &vyre::ir::Program,
inputs: &[&[u8]],
_grid: Option<[u32; 3]>,
outputs: &mut Vec<Vec<u8>>| {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(inputs, outputs);
}
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
outputs[0].extend_from_slice(inputs[5]);
Ok(())
};
run_forward_bitset_closure_into(
&mut closure,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("&mut closure must converge");
assert_eq!(result, seed);
}
#[test]
fn c9_large_frontier_100k_identity_roundtrip() {
let graph = make_graph(100_000, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = full_seed(100_000);
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("100k identity must converge");
assert_eq!(result, seed);
}
#[test]
fn c9_large_frontier_100k_non_convergence() {
reset_counting_counter();
let graph = make_graph(100_000, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed(100_000);
let err = run_forward_bitset_closure_into(
&counting_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
2,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must fail");
assert!(err.contains("did not converge within 2 iterations"));
}
#[test]
fn c10_malformed_input_zero_node_nonzero_seed_word() {
let graph = make_graph(0, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let err = run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&[1u32],
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect_err("must reject nonzero seed for empty domain");
assert!(err.contains("expected exactly 0"));
}
#[test]
fn c1_output_not_modified_after_convergence() {
let graph = make_graph(8, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = vec![0xAAu32];
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
run_forward_bitset_closure_into(
&identity_dispatch,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge again");
assert_eq!(result, seed);
}
#[test]
fn c2_dispatch_grid_override_matches_node_count() {
use std::cell::Cell;
let graph = make_graph(17, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed(17);
let grid = Cell::new([0u32; 3]);
let grid_capture = |program: &vyre::ir::Program,
inputs: &[&[u8]],
g: Option<[u32; 3]>,
outputs: &mut Vec<Vec<u8>>| {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(inputs, outputs);
}
grid.set(g.unwrap_or([0, 0, 0]));
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
outputs[0].extend_from_slice(&pack_words(&seed));
Ok(())
};
run_forward_bitset_closure_into(
&grid_capture,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert_eq!(grid.get(), [17, 1, 1]);
}
#[test]
fn c2_dispatch_grid_override_is_one_for_empty_graph() {
use std::cell::Cell;
let graph = make_graph(0, &[]);
let program = dummy_program();
let mut scratch = FixedPointScratch::default();
let mut result = Vec::new();
let seed = zero_seed(0);
let grid = Cell::new([0u32; 3]);
let grid_capture = |program: &vyre::ir::Program,
inputs: &[&[u8]],
g: Option<[u32; 3]>,
outputs: &mut Vec<Vec<u8>>| {
if is_bitset_equal_program(program) {
return handle_bitset_equal_dispatch(inputs, outputs);
}
grid.set(g.unwrap_or([0, 0, 0]));
if outputs.is_empty() {
outputs.push(Vec::new());
}
outputs[0].clear();
Ok(())
};
run_forward_bitset_closure_into(
&grid_capture,
&graph,
FixedPointAnalysisKind::Generic,
&program,
&seed,
10,
&mut scratch,
&mut result,
test_labels(),
)
.expect("must converge");
assert_eq!(grid.get(), [1, 1, 1]);
}
}