1#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct IfdsProblemFixture {
10 pub num_procs: u32,
11 pub blocks_per_proc: u32,
12 pub facts_per_proc: u32,
13 pub intra_edges: Vec<(u32, u32, u32)>,
14 pub inter_edges: Vec<(u32, u32, u32, u32)>,
15 pub flow_gen: Vec<(u32, u32, u32)>,
16 pub flow_kill: Vec<(u32, u32, u32)>,
17 pub seed_facts: Vec<(u32, u32, u32)>,
18 pub expected_reached: Vec<u32>,
19}
20
21pub struct IfdsProblemBuilder;
23
24impl IfdsProblemBuilder {
25 #[allow(clippy::too_many_arguments)]
26 pub fn random_valid(
27 num_procs: u32,
28 blocks_per_proc: u32,
29 facts_per_proc: u32,
30 intra_edge_count: u32,
31 seed_count: u32,
32 summary_edge_count: u32,
33 seed: u64,
34 ) -> IfdsProblemFixture {
35 assert!(
36 num_procs > 0 && blocks_per_proc > 0 && facts_per_proc > 0,
37 "IfdsProblemBuilder::random_valid requires non-zero dimensions"
38 );
39 let _ = checked_total_nodes(num_procs, blocks_per_proc, facts_per_proc);
40 let mut rng = SplitMix64::new(
41 seed ^ ((num_procs as u64) << 42)
42 ^ ((blocks_per_proc as u64) << 21)
43 ^ facts_per_proc as u64,
44 );
45
46 let mut intra_edges = Vec::with_capacity(intra_edge_count as usize);
47 for edge_idx in 0..intra_edge_count {
48 let proc_id = if edge_idx < num_procs {
49 edge_idx
50 } else {
51 rng.below(num_procs)
52 };
53 let src_block = if blocks_per_proc <= 1 {
54 0
55 } else if edge_idx < blocks_per_proc - 1 {
56 edge_idx
57 } else {
58 rng.below(blocks_per_proc)
59 };
60 let mut dst_block = if blocks_per_proc <= 1 {
61 0
62 } else if edge_idx < blocks_per_proc - 1 {
63 src_block + 1
64 } else {
65 rng.below(blocks_per_proc)
66 };
67 if blocks_per_proc > 1 && dst_block == src_block {
68 dst_block = (dst_block + 1) % blocks_per_proc;
69 }
70 intra_edges.push((proc_id % num_procs, src_block, dst_block));
71 }
72
73 let mut inter_edges = Vec::with_capacity(summary_edge_count as usize);
74 for _ in 0..summary_edge_count {
75 let src_proc = rng.below(num_procs);
76 let mut dst_proc = rng.below(num_procs);
77 if num_procs > 1 && dst_proc == src_proc {
78 dst_proc = (dst_proc + 1) % num_procs;
79 }
80 inter_edges.push((
81 src_proc,
82 rng.below(blocks_per_proc),
83 dst_proc,
84 rng.below(blocks_per_proc),
85 ));
86 }
87
88 let mut flow_gen = Vec::with_capacity(summary_edge_count as usize);
89 let mut flow_kill = Vec::with_capacity((summary_edge_count / 2) as usize);
90 for rule_idx in 0..summary_edge_count {
91 let proc_id = rng.below(num_procs);
92 let block_id = rng.below(blocks_per_proc);
93 let gen_fact = rng.below(facts_per_proc);
94 flow_gen.push((proc_id, block_id, gen_fact));
95 if rule_idx % 2 == 1 {
96 let kill_fact = if facts_per_proc > 1 {
97 1 + rng.below(facts_per_proc - 1)
98 } else {
99 0
100 };
101 flow_kill.push((proc_id, block_id, kill_fact));
102 }
103 }
104
105 let mut seed_facts = Vec::with_capacity(seed_count as usize);
106 for seed_idx in 0..seed_count {
107 let proc_id = if seed_idx < num_procs {
108 seed_idx
109 } else {
110 rng.below(num_procs)
111 };
112 let block_id = if blocks_per_proc > 1 && seed_idx == 0 {
113 0
114 } else {
115 rng.below(blocks_per_proc)
116 };
117 let fact_id = if facts_per_proc > 1 && seed_idx == 0 {
118 0
119 } else {
120 rng.below(facts_per_proc)
121 };
122 seed_facts.push((proc_id % num_procs, block_id, fact_id));
123 }
124
125 let expected_reached = expected_reachability(
126 num_procs,
127 blocks_per_proc,
128 facts_per_proc,
129 &intra_edges,
130 &inter_edges,
131 &flow_gen,
132 &flow_kill,
133 &seed_facts,
134 );
135
136 IfdsProblemFixture {
137 num_procs,
138 blocks_per_proc,
139 facts_per_proc,
140 intra_edges,
141 inter_edges,
142 flow_gen,
143 flow_kill,
144 seed_facts,
145 expected_reached,
146 }
147 }
148
149 pub fn adversarial_zero_dimensions() -> IfdsProblemFixture {
150 IfdsProblemFixture {
151 num_procs: 0,
152 blocks_per_proc: 1,
153 facts_per_proc: 1,
154 intra_edges: Vec::new(),
155 inter_edges: Vec::new(),
156 flow_gen: Vec::new(),
157 flow_kill: Vec::new(),
158 seed_facts: Vec::new(),
159 expected_reached: Vec::new(),
160 }
161 }
162
163 pub fn adversarial_overflow_shape() -> IfdsProblemFixture {
164 IfdsProblemFixture {
165 num_procs: 4097,
166 blocks_per_proc: 1,
167 facts_per_proc: 1,
168 intra_edges: Vec::new(),
169 inter_edges: Vec::new(),
170 flow_gen: Vec::new(),
171 flow_kill: Vec::new(),
172 seed_facts: Vec::new(),
173 expected_reached: Vec::new(),
174 }
175 }
176}
177
178#[allow(clippy::too_many_arguments)]
179fn expected_reachability(
180 num_procs: u32,
181 blocks_per_proc: u32,
182 facts_per_proc: u32,
183 intra_edges: &[(u32, u32, u32)],
184 inter_edges: &[(u32, u32, u32, u32)],
185 flow_gen: &[(u32, u32, u32)],
186 flow_kill: &[(u32, u32, u32)],
187 seed_facts: &[(u32, u32, u32)],
188) -> Vec<u32> {
189 let total_nodes = checked_total_nodes(num_procs, blocks_per_proc, facts_per_proc);
190 let mut killed = vec![false; total_nodes];
191 for &(proc_id, block_id, fact_id) in flow_kill {
192 killed[dense_index(proc_id, block_id, fact_id, blocks_per_proc, facts_per_proc)] = true;
193 }
194
195 let mut visited = vec![false; total_nodes];
196 let mut queue = Vec::with_capacity(seed_facts.len());
197 for &(proc_id, block_id, fact_id) in seed_facts {
198 mark_reached(
199 proc_id,
200 block_id,
201 fact_id,
202 blocks_per_proc,
203 facts_per_proc,
204 &mut visited,
205 &mut queue,
206 );
207 }
208
209 let mut head = 0usize;
210 while head < queue.len() {
211 let (proc_id, block_id, fact_id) = queue[head];
212 head += 1;
213
214 for &(edge_proc, src_block, dst_block) in intra_edges {
215 if edge_proc != proc_id || src_block != block_id {
216 continue;
217 }
218 let current_idx =
219 dense_index(proc_id, block_id, fact_id, blocks_per_proc, facts_per_proc);
220 if !killed[current_idx] {
221 mark_reached(
222 proc_id,
223 dst_block,
224 fact_id,
225 blocks_per_proc,
226 facts_per_proc,
227 &mut visited,
228 &mut queue,
229 );
230 }
231 if fact_id == 0 {
232 for &(gen_proc, gen_block, gen_fact) in flow_gen {
233 if gen_proc == proc_id && gen_block == block_id {
234 mark_reached(
235 proc_id,
236 dst_block,
237 gen_fact,
238 blocks_per_proc,
239 facts_per_proc,
240 &mut visited,
241 &mut queue,
242 );
243 }
244 }
245 }
246 }
247
248 for &(src_proc, src_block, dst_proc, dst_block) in inter_edges {
249 if src_proc == proc_id && src_block == block_id {
250 mark_reached(
251 dst_proc,
252 dst_block,
253 fact_id,
254 blocks_per_proc,
255 facts_per_proc,
256 &mut visited,
257 &mut queue,
258 );
259 }
260 }
261 }
262
263 visited
264 .into_iter()
265 .enumerate()
266 .filter_map(|(dense_id, reached)| reached.then_some(dense_id as u32))
267 .collect()
268}
269
270fn mark_reached(
271 proc_id: u32,
272 block_id: u32,
273 fact_id: u32,
274 blocks_per_proc: u32,
275 facts_per_proc: u32,
276 visited: &mut [bool],
277 queue: &mut Vec<(u32, u32, u32)>,
278) {
279 let idx = dense_index(proc_id, block_id, fact_id, blocks_per_proc, facts_per_proc);
280 if !visited[idx] {
281 visited[idx] = true;
282 queue.push((proc_id, block_id, fact_id));
283 }
284}
285
286fn checked_total_nodes(num_procs: u32, blocks_per_proc: u32, facts_per_proc: u32) -> usize {
287 let total = (num_procs as u64)
288 .checked_mul(blocks_per_proc as u64)
289 .and_then(|value| value.checked_mul(facts_per_proc as u64))
290 ;
291 assert!(
292 total.is_some(),
293 "IFDS fixture node count overflows u64"
294 );
295 let total = total.unwrap_or(u64::MAX);
296 assert!(
297 total <= u32::MAX as u64,
298 "IFDS fixture node count {total} exceeds u32 dense domain"
299 );
300 assert!(
301 total <= usize::MAX as u64,
302 "IFDS fixture node count {total} does not fit usize"
303 );
304 total as usize
305}
306
307fn dense_index(
308 proc_id: u32,
309 block_id: u32,
310 fact_id: u32,
311 blocks_per_proc: u32,
312 facts_per_proc: u32,
313) -> usize {
314 let idx = (proc_id as u64)
315 .checked_mul(blocks_per_proc as u64)
316 .and_then(|value| value.checked_mul(facts_per_proc as u64))
317 .and_then(|value| {
318 (block_id as u64)
319 .checked_mul(facts_per_proc as u64)
320 .and_then(|block_offset| value.checked_add(block_offset))
321 })
322 .and_then(|value| value.checked_add(fact_id as u64))
323 ;
324 assert!(idx.is_some(), "IFDS fixture dense index overflows u64");
325 let idx = idx.unwrap_or(u64::MAX);
326 assert!(
327 idx <= usize::MAX as u64,
328 "IFDS fixture dense index {idx} does not fit usize"
329 );
330 idx as usize
331}
332
333#[derive(Debug, Clone, Copy)]
334struct SplitMix64 {
335 state: u64,
336}
337
338impl SplitMix64 {
339 fn new(seed: u64) -> Self {
340 Self {
341 state: seed ^ 0x9e37_79b9_7f4a_7c15,
342 }
343 }
344
345 fn next(&mut self) -> u64 {
346 self.state = self.state.wrapping_add(0x9e37_79b9_7f4a_7c15);
347 let mut z = self.state;
348 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
349 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
350 z ^ (z >> 31)
351 }
352
353 fn below(&mut self, upper: u32) -> u32 {
354 if upper == 0 {
355 return 0;
356 }
357 (self.next() % upper as u64) as u32
358 }
359}