weir/ifds_resident_types/
prepared.rs1use crate::ifds_gpu::IfdsShape;
2
3#[derive(Clone, Debug, PartialEq)]
9pub struct PreparedIfdsCsr {
10 pub(crate) shape: IfdsShape,
11 pub(crate) node_count: u32,
12 pub(crate) words: usize,
13 pub(crate) pg_nodes_b: Vec<u8>,
14 pub(crate) row_ptr_b: Vec<u8>,
15 pub(crate) col_idx_b: Vec<u8>,
16 pub(crate) pg_edge_kind_mask_b: Vec<u8>,
17 pub(crate) pg_node_tags_b: Vec<u8>,
18}
19
20impl PreparedIfdsCsr {
21 #[must_use]
23 pub fn shape(&self) -> IfdsShape {
24 self.shape
25 }
26
27 #[must_use]
29 pub fn node_count(&self) -> u32 {
30 self.node_count
31 }
32
33 #[must_use]
35 pub fn frontier_words(&self) -> usize {
36 self.words
37 }
38
39 #[must_use]
41 pub fn retained_graph_bytes(&self) -> usize {
42 fn padded(bytes: &[u8]) -> &[u8] {
43 const EMPTY_U32: [u8; std::mem::size_of::<u32>()] = [0; std::mem::size_of::<u32>()];
44 if bytes.is_empty() {
45 &EMPTY_U32
46 } else {
47 bytes
48 }
49 }
50
51 let pg_nodes_b = padded(&self.pg_nodes_b);
52 let row_ptr_b = padded(&self.row_ptr_b);
53 let col_idx_b = padded(&self.col_idx_b);
54 let pg_edge_kind_mask_b = padded(&self.pg_edge_kind_mask_b);
55 let pg_node_tags_b = padded(&self.pg_node_tags_b);
56 let node_tag_bytes = if pg_node_tags_b == pg_nodes_b {
57 0
58 } else {
59 pg_node_tags_b.len()
60 };
61 pg_nodes_b.len()
62 + row_ptr_b.len()
63 + col_idx_b.len()
64 + pg_edge_kind_mask_b.len()
65 + node_tag_bytes
66 }
67
68 #[must_use]
70 pub fn stable_layout_hash(&self) -> u64 {
71 super::hash::stable_prepared_ifds_csr_hash(
72 self.shape,
73 self.node_count,
74 self.words,
75 &self.pg_nodes_b,
76 &self.row_ptr_b,
77 &self.col_idx_b,
78 &self.pg_edge_kind_mask_b,
79 &self.pg_node_tags_b,
80 )
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 fn inflated(bytes: &[u8], extra_capacity: usize) -> Vec<u8> {
89 let mut out = Vec::with_capacity(bytes.len() + extra_capacity);
90 out.extend_from_slice(bytes);
91 out
92 }
93
94 fn resident_upload_len(bytes: &[u8]) -> usize {
95 if bytes.is_empty() {
96 std::mem::size_of::<u32>()
97 } else {
98 bytes.len()
99 }
100 }
101
102 fn expected_physical_bytes(prepared: &PreparedIfdsCsr) -> usize {
103 fn padded_owned(bytes: &[u8]) -> Vec<u8> {
104 if bytes.is_empty() {
105 vec![0; std::mem::size_of::<u32>()]
106 } else {
107 bytes.to_vec()
108 }
109 }
110
111 let node_tags_alias_nodes =
112 padded_owned(&prepared.pg_nodes_b) == padded_owned(&prepared.pg_node_tags_b);
113 resident_upload_len(&prepared.pg_nodes_b)
114 + resident_upload_len(&prepared.row_ptr_b)
115 + resident_upload_len(&prepared.col_idx_b)
116 + resident_upload_len(&prepared.pg_edge_kind_mask_b)
117 + if node_tags_alias_nodes {
118 0
119 } else {
120 resident_upload_len(&prepared.pg_node_tags_b)
121 }
122 }
123
124 #[test]
125 fn retained_graph_bytes_counts_physical_resident_uploads_not_vec_capacity() {
126 let prepared = PreparedIfdsCsr {
127 shape: crate::ifds_gpu::IfdsShape {
128 num_procs: 1,
129 blocks_per_proc: 1,
130 facts_per_proc: 1,
131 edge_count: 0,
132 },
133 node_count: 1,
134 words: 1,
135 pg_nodes_b: inflated(&[0, 0, 0, 0], 1024),
136 row_ptr_b: inflated(&[0, 0, 0, 0, 0, 0, 0, 0], 512),
137 col_idx_b: inflated(&[], 256),
138 pg_edge_kind_mask_b: inflated(&[], 128),
139 pg_node_tags_b: inflated(&[0, 0, 0, 0], 4096),
140 };
141
142 assert_eq!(
143 prepared.retained_graph_bytes(),
144 20,
145 "resident retained-byte accounting must count four unique physical buffers: pg_nodes, row_ptr, padded col_idx, padded edge masks"
146 );
147 }
148
149 #[test]
150 fn retained_graph_bytes_matches_generated_alias_and_padding_cases() {
151 for case in 0..2048usize {
152 let node_len = case % 17;
153 let row_len = (case / 3) % 23;
154 let col_len = (case / 5) % 29;
155 let mask_len = (case / 7) % 31;
156 let tag_mode = case % 4;
157 let nodes = (0..node_len)
158 .map(|index| (case.wrapping_add(index * 17) & 0xff) as u8)
159 .collect::<Vec<_>>();
160 let tags = match tag_mode {
161 0 => nodes.clone(),
162 1 => Vec::new(),
163 2 => vec![0; std::mem::size_of::<u32>()],
164 _ => (0..node_len.max(1))
165 .map(|index| (case.wrapping_mul(3).wrapping_add(index * 11) | 1) as u8)
166 .collect(),
167 };
168 let prepared = PreparedIfdsCsr {
169 shape: crate::ifds_gpu::IfdsShape {
170 num_procs: 1,
171 blocks_per_proc: 1,
172 facts_per_proc: 1,
173 edge_count: col_len as u32,
174 },
175 node_count: node_len.max(1) as u32,
176 words: 1,
177 pg_nodes_b: inflated(&nodes, case % 13),
178 row_ptr_b: inflated(&vec![0x11; row_len], case % 11),
179 col_idx_b: inflated(&vec![0x22; col_len], case % 7),
180 pg_edge_kind_mask_b: inflated(&vec![0x33; mask_len], case % 5),
181 pg_node_tags_b: inflated(&tags, case % 19),
182 };
183
184 assert_eq!(
185 prepared.retained_graph_bytes(),
186 expected_physical_bytes(&prepared),
187 "case {case} must account padded physical resident bytes and node/tag aliasing"
188 );
189 }
190 }
191}