use super::*;
#[test]
fn resident_scratch_can_be_reused_across_independent_solves() {
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_scratch(&dispatch, &prepared)
.expect("scratch allocation should succeed");
assert_eq!(
dispatch.buffers.borrow().len(),
7,
"scratch allocation should create only frontier and changed buffers"
);
let allocate_calls_after_scratch = dispatch.allocate_calls.get();
let seed_a = [(0, 0, 0)];
let seed_b = [(0, 2, 0)];
let mut host_scratch = ResidentIfdsHostScratch::default();
let mut results = Vec::with_capacity(1);
solve_resident_prepared_many_with_scratch_and_host_into_via(
&dispatch,
&prepared,
&scratch,
&mut host_scratch,
&[&seed_a[..]],
8,
&mut results,
)
.expect("first scratch solve should converge");
assert_eq!(results, vec![vec![0]]);
let outer_ptr = results.as_ptr();
let row_ptr = results[0].as_ptr();
let row_capacity = results[0].capacity();
let frontier_ptr = host_scratch.frontier.as_ptr();
let frontier_bytes_ptr = host_scratch.frontier_bytes.as_ptr();
let changed_bytes_ptr = host_scratch.changed_bytes.as_ptr();
assert!(host_scratch.step_program.is_some());
solve_resident_prepared_many_with_scratch_and_host_into_via(
&dispatch,
&prepared,
&scratch,
&mut host_scratch,
&[&seed_b[..]],
8,
&mut results,
)
.expect("second scratch solve should converge");
assert_eq!(results, vec![vec![2048]]);
assert_eq!(
results.as_ptr(),
outer_ptr,
"caller-owned result Vec shell must be reused across scratch solves"
);
assert_eq!(
results[0].as_ptr(),
row_ptr,
"caller-owned result row allocation must be reused across scratch solves"
);
assert_eq!(
results[0].capacity(),
row_capacity,
"caller-owned result row capacity must be preserved across scratch solves"
);
assert_eq!(
host_scratch.frontier.as_ptr(),
frontier_ptr,
"host frontier decode buffer must be reused across scratch solves"
);
assert_eq!(
host_scratch.frontier_bytes.as_ptr(),
frontier_bytes_ptr,
"host frontier readback bytes must be reused across scratch solves"
);
assert_eq!(
host_scratch.changed_bytes.as_ptr(),
changed_bytes_ptr,
"host changed-flag readback bytes must be reused across scratch solves"
);
assert_eq!(
dispatch.buffers.borrow().len(),
7,
"reusing scratch across independent solves must retain only frontier and changed resident buffers"
);
assert_eq!(
dispatch.allocate_calls.get(),
allocate_calls_after_scratch,
"reusing single-query scratch must not allocate transient seed staging buffers"
);
free_resident_ifds_scratch(&dispatch, scratch).expect("scratch free should succeed");
}
#[test]
fn resident_scratch_seed_capacity_is_explicit_and_allocation_free() {
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_scratch_with_seed_capacity(&dispatch, &prepared, 2)
.expect("scratch allocation with two staged seed facts should succeed");
assert_eq!(scratch.max_seed_facts(), 2);
assert_eq!(
dispatch.buffers.borrow().len(),
7,
"single-query scratch should allocate only fixed frontier and changed buffers"
);
let allocate_calls_after_scratch = dispatch.allocate_calls.get();
let seeds = [(0, 0, 0), (0, 2, 0)];
let _ = solve_resident_prepared_many_with_scratch_via(
&dispatch,
&prepared,
&scratch,
&[&seeds[..]],
8,
)
.expect("scratch solve with two staged seed facts should converge");
assert_eq!(
dispatch.allocate_calls.get(),
allocate_calls_after_scratch,
"explicit seed-capacity scratch must not allocate transient seed staging buffers"
);
free_resident_ifds_scratch(&dispatch, scratch).expect("scratch free should succeed");
}
#[test]
fn resident_scratch_solve_uses_one_changed_readback_after_gpu_iteration_window() {
let dispatch = FakeResidentDispatch::new_with_program_graph_buffers(6);
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 scratch = allocate_resident_ifds_scratch(&dispatch, &prepared)
.expect("scratch allocation should succeed");
let reached = solve_resident_prepared_many_with_scratch_via(
&dispatch,
&prepared,
&scratch,
&[&[(0, 0, 0)][..]],
16,
)
.expect("scratch solve should converge inside the speculative GPU window");
assert_eq!(reached, vec![vec![0]]);
assert_eq!(
dispatch.dispatches.get(),
6,
"single-query scratch solve should stop after GPU convergence instead of exhausting the full budget"
);
assert_eq!(
dispatch.sequence_read_ranges_calls.get(),
0,
"single-query scratch solve must avoid the broken resident sequence launcher on CUDA"
);
assert_eq!(
*dispatch.downloads.borrow(),
vec![4, 4, 4, 4, 4, 4, words * 4],
"single-query scratch solve must poll one changed flag per GPU step and download one final frontier"
);
free_resident_ifds_scratch(&dispatch, scratch).expect("scratch free should succeed");
}