use super::*;
#[test]
fn resident_parallel_batch_solver_dispatches_all_seed_sets_together() {
let dispatch = FakeResidentDispatch::new_with_program_graph_buffers(1);
let prepared = ResidentPreparedIfdsCsr {
shape: IfdsShape {
num_procs: 1,
blocks_per_proc: 4,
facts_per_proc: 1,
edge_count: 0,
},
node_count: 4,
words: 1,
pg_nodes: 0,
row_ptr: 1,
col_idx: 2,
pg_edge_kind_mask: 3,
pg_node_tags: 4,
};
let seed_a = [(0, 0, 0)];
let seed_b = [(0, 1, 0)];
let seed_c = [(0, 2, 0)];
let got = solve_resident_prepared_many_parallel_via(
&dispatch,
&prepared,
&[&seed_a[..], &seed_b[..], &seed_c[..]],
8,
)
.expect("fake resident parallel batch solver should converge");
assert_eq!(got, vec![vec![0], vec![1024], vec![2048]]);
assert_eq!(
dispatch.dispatches.get(),
8,
"parallel batch solve must dispatch all seed sets together without host polling between iterations"
);
assert_eq!(
dispatch.sequence_read_ranges_calls.get(),
1,
"parallel batch solve must batch the fixed-point window and compact readbacks behind one resident sequence-read call"
);
assert_eq!(
*dispatch.downloads.borrow(),
vec![4, 12],
"parallel batch solve should download one final changed flag, then one final frontier matrix"
);
}
#[test]
fn resident_parallel_batch_convergence_download_is_constant_in_query_count() {
let dispatch = FakeResidentDispatch::new_with_program_graph_buffers(4);
let node_count = 32_768;
let words = ifds_words(node_count);
let prepared = ResidentPreparedIfdsCsr {
shape: IfdsShape {
num_procs: 1024,
blocks_per_proc: 32,
facts_per_proc: 1,
edge_count: 0,
},
node_count,
words,
pg_nodes: 0,
row_ptr: 1,
col_idx: 2,
pg_edge_kind_mask: 3,
pg_node_tags: 4,
};
let seed_a = [(0, 0, 0)];
let seed_b = [(1, 0, 0)];
let seed_c = [(2, 0, 0)];
let seed_d = [(3, 0, 0)];
let seed_e = [(4, 0, 0)];
let got = solve_resident_prepared_many_parallel_via(
&dispatch,
&prepared,
&[
&seed_a[..],
&seed_b[..],
&seed_c[..],
&seed_d[..],
&seed_e[..],
],
8,
)
.expect("fake resident parallel batch solver should converge");
assert_eq!(got.len(), 5);
assert_eq!(dispatch.dispatches.get(), 8);
let downloads = dispatch.downloads.borrow();
assert_eq!(downloads.len(), 2);
assert_eq!(downloads[0], std::mem::size_of::<u32>());
assert_eq!(downloads[1], 5 * words * std::mem::size_of::<u32>());
let convergence_downloaded = downloads[0];
assert_eq!(
convergence_downloaded,
4,
"parallel IFDS batch convergence must download one final changed flag total, not synchronize per iteration or per query"
);
assert!(
!dispatch
.uploaded_lengths
.borrow()
.iter()
.any(|&len| len == 4),
"parallel IFDS batch convergence must not upload a 4-byte changed reset every iteration"
);
}
#[test]
fn resident_parallel_batch_seed_setup_does_not_upload_frontier_matrix() {
let dispatch = FakeResidentDispatch::new_with_program_graph_buffers(1);
let node_count = 32_768;
let words = ifds_words(node_count);
let prepared = ResidentPreparedIfdsCsr {
shape: IfdsShape {
num_procs: 1024,
blocks_per_proc: 32,
facts_per_proc: 1,
edge_count: 0,
},
node_count,
words,
pg_nodes: 0,
row_ptr: 1,
col_idx: 2,
pg_edge_kind_mask: 3,
pg_node_tags: 4,
};
let seed_a = [(0, 0, 0)];
let seed_b = [(1, 0, 0)];
let seed_c = [(2, 0, 0)];
let seed_d = [(3, 0, 0)];
let seed_e = [(4, 0, 0)];
let _ = solve_resident_prepared_many_parallel_via(
&dispatch,
&prepared,
&[
&seed_a[..],
&seed_b[..],
&seed_c[..],
&seed_d[..],
&seed_e[..],
],
8,
)
.expect("fake resident parallel batch solver should converge");
let full_frontier_matrix_bytes = 5 * words * std::mem::size_of::<u32>();
assert!(
!dispatch
.uploaded_lengths
.borrow()
.iter()
.any(|&len| len == full_frontier_matrix_bytes),
"parallel IFDS batch seed setup must scatter compact seeds on GPU instead of uploading the full frontier matrix"
);
}
#[test]
fn resident_parallel_scratch_can_be_reused_across_batches() {
let dispatch = FakeResidentDispatch::new_with_program_graph_buffers(1);
let prepared = ResidentPreparedIfdsCsr {
shape: IfdsShape {
num_procs: 1,
blocks_per_proc: 4,
facts_per_proc: 1,
edge_count: 0,
},
node_count: 4,
words: 1,
pg_nodes: 0,
row_ptr: 1,
col_idx: 2,
pg_edge_kind_mask: 3,
pg_node_tags: 4,
};
let scratch = allocate_resident_ifds_parallel_scratch(&dispatch, &prepared, 3)
.expect("parallel scratch allocation should succeed");
let allocations_after_scratch = dispatch.allocate_calls.get();
assert_eq!(
dispatch.buffers.borrow().len(),
9,
"parallel scratch allocation should create frontier, changed flag, and reusable seed staging buffers"
);
let seed_a = [(0, 0, 0)];
let seed_b = [(0, 1, 0)];
let seed_c = [(0, 2, 0)];
let first = solve_resident_prepared_many_parallel_with_scratch_via(
&dispatch,
&prepared,
&scratch,
&[&seed_a[..], &seed_b[..]],
8,
)
.expect("first parallel scratch solve should converge");
let second = solve_resident_prepared_many_parallel_with_scratch_via(
&dispatch,
&prepared,
&scratch,
&[&seed_c[..]],
8,
)
.expect("second parallel scratch solve should converge");
assert_eq!(first, vec![vec![0], vec![1024]]);
assert_eq!(second, vec![vec![2048]]);
assert_eq!(
dispatch.allocate_calls.get(),
allocations_after_scratch,
"reusing parallel scratch must not allocate resident buffers during solve"
);
assert_eq!(
dispatch.buffers.borrow().len(),
9,
"reusing parallel scratch across batches must not allocate more resident buffers"
);
free_resident_ifds_parallel_scratch(&dispatch, scratch)
.expect("parallel scratch free should succeed");
}
#[test]
fn resident_parallel_scratch_clears_stale_changed_history_on_gpu() {
let dispatch = FakeResidentDispatch::new_with_program_graph_buffers(1);
let prepared = ResidentPreparedIfdsCsr {
shape: IfdsShape {
num_procs: 1,
blocks_per_proc: 4,
facts_per_proc: 1,
edge_count: 0,
},
node_count: 4,
words: 1,
pg_nodes: 0,
row_ptr: 1,
col_idx: 2,
pg_edge_kind_mask: 3,
pg_node_tags: 4,
};
let scratch =
allocate_resident_ifds_parallel_scratch_with_iterations(&dispatch, &prepared, 2, 8)
.expect("parallel scratch allocation should succeed");
dispatch.buffers.borrow_mut()[scratch.changed] = vec![0xFF; scratch.changed_byte_len];
let seed = [(0, 0, 0)];
let got = solve_resident_prepared_many_parallel_with_scratch_via(
&dispatch,
&prepared,
&scratch,
&[&seed[..]],
8,
)
.expect("stale changed history must be cleared before solve");
assert_eq!(got, vec![vec![0]]);
assert!(
!dispatch
.uploaded_lengths
.borrow()
.iter()
.any(|&len| len == 4),
"changed-flag clearing must stay GPU-resident, not a 4-byte host reset upload"
);
free_resident_ifds_parallel_scratch(&dispatch, scratch)
.expect("parallel scratch free should succeed");
}