1#![allow(clippy::too_many_arguments)]
28use std::collections::{HashMap, HashSet};
29use std::hash::Hash;
30
31use vyre::ir::Program;
32use vyre_driver::backend::{acquire_preferred_dispatch_backend, DispatchConfig};
33use vyre_primitives::graph::csr_forward_traverse::csr_forward_traverse;
34use vyre_primitives::graph::dominator_tree::{try_dominator_tree_program, IDOM_NONE};
35use vyre_primitives::graph::program_graph::ProgramGraphShape;
36
37pub(crate) const OP_ID: &str = "weir::ssa";
38
39#[must_use]
60pub fn ssa_phi_placement_step(
61 shape: ProgramGraphShape,
62 frontier_in: &str,
63 frontier_out: &str,
64) -> Program {
65 csr_forward_traverse(shape, frontier_in, frontier_out, 0xFFFF_FFFF)
66}
67
68inventory::submit! {
69 vyre_harness::OpEntry::new(
70 OP_ID,
71 || ssa_phi_placement_step(ProgramGraphShape::new(4, 4), "fin", "fout"),
72 Some(|| {
73 let to_bytes = crate::dispatch_decode::pack_u32;
74 vec![vec![
79 to_bytes(&[0, 0, 0, 0]), to_bytes(&[0, 2, 3, 4, 4]), to_bytes(&[1, 2, 3, 3]), to_bytes(&[1, 1, 1, 1]), to_bytes(&[0, 0, 0, 0]), to_bytes(&[0b0001]), to_bytes(&[0b0001]), ]]
87 }),
88 Some(|| {
89 let to_bytes = crate::dispatch_decode::pack_u32;
90 vec![vec![to_bytes(&[0b0111])]]
93 }),
94 )
95}
96
97inventory::submit! {
98 vyre_harness::ConvergenceContract {
99 op_id: OP_ID,
100 max_iterations: 64,
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct SsaForm {
107 pub phi_nodes: HashMap<u32, Vec<u32>>,
109 pub renamed_usages: HashMap<u32, u32>,
111 pub def_use_chains: HashMap<u32, Vec<u32>>,
113}
114
115#[derive(Debug, Clone, PartialEq)]
116pub struct Block {
118 pub id: u32,
120 pub preds: Vec<u32>,
122 pub succs: Vec<u32>,
124 pub defs: HashSet<u32>,
126 pub uses: HashSet<u32>,
128}
129
130#[derive(Debug, Clone, PartialEq)]
131pub struct Cfg {
133 pub entry: u32,
135 pub blocks: HashMap<u32, Block>,
137}
138
139pub const DOMINATOR_GPU_THRESHOLD: u32 = 1_000;
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub(crate) enum DominatorPath {
146 Cpu,
148 Gpu,
150 GpuFallback,
152}
153
154pub fn compute_dominators(cfg: &Cfg) -> Result<HashMap<u32, u32>, &'static str> {
156 try_compute_dominators(cfg).map_err(|error| {
157 if error.contains("entry") {
158 "SSA dominator construction failed: entry block not found"
159 } else {
160 "SSA dominator construction failed"
161 }
162 })
163}
164
165pub fn try_compute_dominators(cfg: &Cfg) -> Result<HashMap<u32, u32>, String> {
172 try_compute_dominators_detailed(cfg).map(|(doms, _path)| doms)
173}
174
175pub(crate) fn try_compute_dominators_detailed(
177 cfg: &Cfg,
178) -> Result<(HashMap<u32, u32>, DominatorPath), String> {
179 if !cfg.blocks.contains_key(&cfg.entry) {
180 return Err("CFG entry block not found".to_string());
181 }
182
183 let node_count = cfg.blocks.len() as u32;
184 if node_count > DOMINATOR_GPU_THRESHOLD {
185 match try_compute_dominators_gpu(cfg) {
186 Ok(doms) => return Ok((doms, DominatorPath::Gpu)),
187 Err(_) => {
188 let doms = try_compute_dominators_cpu(cfg)?;
189 return Ok((doms, DominatorPath::GpuFallback));
190 }
191 }
192 }
193
194 let doms = try_compute_dominators_cpu(cfg)?;
195 Ok((doms, DominatorPath::Cpu))
196}
197
198pub fn try_compute_dominators_cpu(cfg: &Cfg) -> Result<HashMap<u32, u32>, String> {
202 let mut doms: HashMap<u32, u32> = HashMap::new();
203 reserve_hash_map(&mut doms, cfg.blocks.len(), "ssa dominator map")?;
204 doms.insert(cfg.entry, cfg.entry);
205
206 let mut post_order =
207 crate::staging_reserve::reserved_vec(cfg.blocks.len(), "ssa dominator postorder")
208 .map_err(|error| format!("SSA dominator postorder allocation failed: {error}"))?;
209 let mut visited = HashSet::new();
210 reserve_hash_set(
211 &mut visited,
212 cfg.blocks.len(),
213 "ssa dominator DFS visited set",
214 )?;
215 let mut stack = vec![(cfg.entry, false)];
217 while let Some((u, processed)) = stack.pop() {
218 if processed {
219 post_order.push(u);
220 continue;
221 }
222 if !visited.insert(u) {
223 continue;
224 }
225 stack.push((u, true));
226 if let Some(block) = cfg.blocks.get(&u) {
227 for &v in block.succs.iter().rev() {
228 if !visited.contains(&v) {
229 stack.push((v, false));
230 }
231 }
232 }
233 }
234
235 post_order.reverse();
237
238 let post_order_domain = crate::dense_domain::dense_domain_for_keys(
239 post_order.iter().copied().chain(cfg.blocks.keys().copied()),
240 )?;
241 let mut post_order_idx = crate::dense_domain::DenseU32Slots::<usize>::new(
242 post_order_domain,
243 "ssa dominator postorder index",
244 )?;
245 const MISSING_POSTORDER: usize = usize::MAX;
246 for (i, &u) in post_order.iter().enumerate() {
247 post_order_idx.insert(u, i)?;
248 }
249
250 let intersect = |mut b1: u32, mut b2: u32, doms: &HashMap<u32, u32>| -> u32 {
251 let post_idx = |block: u32| {
252 post_order_idx
253 .get(block)
254 .copied()
255 .unwrap_or(MISSING_POSTORDER)
256 };
257 while b1 != b2 {
258 while post_idx(b1) > post_idx(b2) {
259 b1 = *doms.get(&b1).unwrap_or(&b1);
260 }
261 while post_idx(b2) > post_idx(b1) {
262 b2 = *doms.get(&b2).unwrap_or(&b2);
263 }
264 }
265 b1
266 };
267
268 let mut changed = true;
269 while changed {
270 changed = false;
271 for &b in post_order.iter().skip(1) {
272 if let Some(block) = cfg.blocks.get(&b) {
273 let mut new_idom: Option<u32> = None;
274 for &p in &block.preds {
275 if doms.contains_key(&p) {
276 if let Some(n) = new_idom {
277 new_idom = Some(intersect(p, n, &doms));
278 } else {
279 new_idom = Some(p);
280 }
281 }
282 }
283 if let Some(new_idom) = new_idom {
284 if doms.get(&b) != Some(&new_idom) {
285 doms.insert(b, new_idom);
286 changed = true;
287 }
288 }
289 }
290 }
291 }
292
293 Ok(doms)
294}
295
296fn try_compute_dominators_gpu(cfg: &Cfg) -> Result<HashMap<u32, u32>, String> {
301 let mut ids: Vec<u32> = cfg
303 .blocks
304 .keys()
305 .copied()
306 .filter(|&id| id != cfg.entry)
307 .collect();
308 ids.sort_unstable();
309 ids.insert(0, cfg.entry);
310
311 let node_count = ids.len() as u32;
312 let mut id_to_idx = HashMap::new();
313 reserve_hash_map(&mut id_to_idx, ids.len(), "ssa gpu id map")?;
314 for (idx, &id) in ids.iter().enumerate() {
315 id_to_idx.insert(id, idx as u32);
316 }
317
318 let mut forward_offsets: Vec<u32> = Vec::new();
320 crate::dispatch_decode::try_write_zero_words(
321 &mut forward_offsets,
322 ids.len() + 1,
323 "ssa gpu forward offsets",
324 )?;
325 let mut pred_offsets: Vec<u32> = Vec::new();
326 crate::dispatch_decode::try_write_zero_words(
327 &mut pred_offsets,
328 ids.len() + 1,
329 "ssa gpu pred offsets",
330 )?;
331
332 for (&block_id, block) in &cfg.blocks {
334 let src_idx = id_to_idx[&block_id];
335 for &succ_id in &block.succs {
336 if id_to_idx.contains_key(&succ_id) {
337 forward_offsets[src_idx as usize + 1] += 1;
338 }
339 }
340 let dst_idx = src_idx;
341 for &pred_id in &block.preds {
342 if id_to_idx.contains_key(&pred_id) {
343 pred_offsets[dst_idx as usize + 1] += 1;
344 }
345 }
346 }
347
348 for i in 1..=ids.len() {
350 forward_offsets[i] += forward_offsets[i - 1];
351 pred_offsets[i] += pred_offsets[i - 1];
352 }
353
354 let forward_target_count = forward_offsets[ids.len()] as usize;
355 let mut forward_targets: Vec<u32> = Vec::new();
356 crate::staging_reserve::reserve_vec(
357 &mut forward_targets,
358 forward_target_count,
359 "ssa gpu forward targets",
360 )?;
361 forward_targets.resize(forward_target_count, 0);
362
363 let pred_target_count = pred_offsets[ids.len()] as usize;
364 let mut pred_targets: Vec<u32> = Vec::new();
365 crate::staging_reserve::reserve_vec(
366 &mut pred_targets,
367 pred_target_count,
368 "ssa gpu pred targets",
369 )?;
370 pred_targets.resize(pred_target_count, 0);
371
372 let mut forward_cursor = forward_offsets.clone();
373 let mut pred_cursor = pred_offsets.clone();
374
375 for (&block_id, block) in &cfg.blocks {
376 let src_idx = id_to_idx[&block_id];
377 for &succ_id in &block.succs {
378 if let Some(&dst_idx) = id_to_idx.get(&succ_id) {
379 let slot = forward_cursor[src_idx as usize] as usize;
380 forward_targets[slot] = dst_idx;
381 forward_cursor[src_idx as usize] += 1;
382 }
383 }
384 let dst_idx = src_idx;
385 for &pred_id in &block.preds {
386 if let Some(&src_idx_pred) = id_to_idx.get(&pred_id) {
387 let slot = pred_cursor[dst_idx as usize] as usize;
388 pred_targets[slot] = src_idx_pred;
389 pred_cursor[dst_idx as usize] += 1;
390 }
391 }
392 }
393
394 let edge_count = forward_targets.len() as u32;
395 let pred_edge_count = pred_targets.len() as u32;
396
397 let program = try_dominator_tree_program(node_count, edge_count, pred_edge_count, "idom_out")
398 .map_err(|e| format!("dominator_tree program build failed: {e}"))?;
399
400 let mut inputs: Vec<Vec<u8>> = Vec::with_capacity(6);
401 inputs.push(crate::dispatch_decode::try_pack_u32(
402 &forward_offsets,
403 "ssa gpu forward offsets",
404 )?);
405 inputs.push(crate::dispatch_decode::try_pack_u32(
406 &forward_targets,
407 "ssa gpu forward targets",
408 )?);
409 inputs.push(crate::dispatch_decode::try_pack_u32(
410 &pred_offsets,
411 "ssa gpu predecessor offsets",
412 )?);
413 inputs.push(crate::dispatch_decode::try_pack_u32(
414 &pred_targets,
415 "ssa gpu predecessor targets",
416 )?);
417 let idom_out_len = (node_count as usize)
418 .checked_mul(4)
419 .ok_or("idom_out byte length overflowed usize")?;
420 let dt_depth_len = (node_count as usize)
421 .checked_mul(4)
422 .ok_or("dt_depth byte length overflowed usize")?;
423 inputs.push(vec![0u8; idom_out_len]);
424 inputs.push(vec![0u8; dt_depth_len]);
425
426 let backend = acquire_preferred_dispatch_backend()
427 .map_err(|e| format!("no dispatch backend available: {e}"))?;
428
429 let mut config = DispatchConfig::default();
430 config.grid_override = Some([1, 1, 1]);
431 let outputs = backend
432 .dispatch(&program, &inputs, &config)
433 .map_err(|e| format!("dominator_tree dispatch failed: {e}"))?;
434
435 if outputs.len() != 2 {
436 return Err(format!(
437 "dominator_tree dispatch returned {} output buffers, expected 2 (idom_out + dt_depth)",
438 outputs.len()
439 ));
440 }
441
442 let mut idoms = Vec::new();
443 crate::staging_reserve::reserve_vec(&mut idoms, node_count as usize, "ssa gpu idom decode")?;
444 crate::dispatch_decode::unpack_exact_u32_into(
445 &outputs[0],
446 node_count as usize,
447 "idom_out",
448 &mut idoms,
449 )?;
450
451 let mut doms = HashMap::new();
452 reserve_hash_map(&mut doms, ids.len(), "ssa dominator map")?;
453 for (idx, &idom) in idoms.iter().enumerate() {
454 if idom == IDOM_NONE {
455 continue;
456 }
457 let original_block = ids[idx];
458 let original_idom = ids[idom as usize];
459 doms.insert(original_block, original_idom);
460 }
461
462 Ok(doms)
463}
464
465#[derive(Clone, Copy, Debug, PartialEq, Eq)]
467pub struct Ssa;
468
469impl super::soundness::SoundnessTagged for Ssa {
470 fn soundness(&self) -> super::soundness::Soundness {
471 super::soundness::Soundness::Exact
472 }
473}
474
475#[cfg(any(test, feature = "legacy-infallible"))]
477pub fn compute_dominance_frontiers(
478 cfg: &Cfg,
479 doms: &HashMap<u32, u32>,
480) -> HashMap<u32, HashSet<u32>> {
481 try_compute_dominance_frontiers(cfg, doms)
482 .expect("SSA dominance-frontier allocation failed in legacy infallible caller")
483}
484
485pub fn try_compute_dominance_frontiers(
487 cfg: &Cfg,
488 doms: &HashMap<u32, u32>,
489) -> Result<HashMap<u32, HashSet<u32>>, String> {
490 let mut df: HashMap<u32, HashSet<u32>> = HashMap::new();
491 reserve_hash_map(&mut df, cfg.blocks.len(), "ssa dominance frontier map")?;
492 for &b in cfg.blocks.keys() {
493 df.insert(b, HashSet::new());
494 }
495
496 for (&b, block) in &cfg.blocks {
497 if block.preds.len() >= 2 {
498 for &p in &block.preds {
499 let mut runner = p;
500 while runner != *doms.get(&b).unwrap_or(&b) {
501 let frontier = df.entry(runner).or_default();
502 reserve_hash_set(
503 frontier,
504 frontier.len().checked_add(1).ok_or_else(|| {
505 "SSA dominance frontier set length overflowed usize".to_string()
506 })?,
507 "ssa dominance frontier set",
508 )?;
509 frontier.insert(b);
510 runner = *doms.get(&runner).unwrap_or(&runner);
511 }
512 }
513 }
514 }
515 Ok(df)
516}
517
518#[cfg(any(test, feature = "legacy-infallible"))]
520pub fn place_phi_nodes(cfg: &Cfg, df: &HashMap<u32, HashSet<u32>>) -> HashMap<u32, Vec<u32>> {
521 try_place_phi_nodes(cfg, df)
522 .expect("SSA phi placement allocation failed in legacy infallible caller")
523}
524
525pub fn try_place_phi_nodes(
528 cfg: &Cfg,
529 df: &HashMap<u32, HashSet<u32>>,
530) -> Result<HashMap<u32, Vec<u32>>, String> {
531 let mut phi_nodes: HashMap<u32, Vec<u32>> = HashMap::new();
532 reserve_hash_map(&mut phi_nodes, cfg.blocks.len(), "ssa phi node map")?;
533
534 let mut defs: HashMap<u32, HashSet<u32>> = HashMap::new();
536 reserve_hash_map(&mut defs, cfg.blocks.len(), "ssa definition-block map")?;
537 let mut vars: HashSet<u32> = HashSet::new();
538 reserve_hash_set(&mut vars, cfg.blocks.len(), "ssa variable set")?;
539
540 for (&b, block) in &cfg.blocks {
541 for &v in &block.defs {
542 let blocks = defs.entry(v).or_default();
543 reserve_hash_set(
544 blocks,
545 blocks.len().checked_add(1).ok_or_else(|| {
546 "SSA definition block set length overflowed usize".to_string()
547 })?,
548 "ssa definition block set",
549 )?;
550 blocks.insert(b);
551 let next_vars_len = vars
552 .len()
553 .checked_add(1)
554 .ok_or_else(|| "SSA variable set length overflowed usize".to_string())?;
555 reserve_hash_set(&mut vars, next_vars_len, "ssa variable set")?;
556 vars.insert(v);
557 }
558 }
559
560 for &v in &vars {
561 let Some(var_defs) = defs.get(&v) else {
562 continue;
563 };
564 let mut worklist: Vec<u32> = Vec::new();
565 crate::staging_reserve::reserve_vec(
566 &mut worklist,
567 var_defs.len(),
568 "ssa phi placement worklist",
569 )?;
570 worklist.extend(var_defs.iter().copied());
571 let mut in_worklist: HashSet<u32> = HashSet::new();
572 reserve_hash_set(&mut in_worklist, var_defs.len(), "ssa phi worklist set")?;
573 in_worklist.extend(var_defs.iter().copied());
574 let mut inserted_phi: HashSet<u32> = HashSet::new();
575 reserve_hash_set(&mut inserted_phi, df.len(), "ssa inserted-phi set")?;
576
577 while let Some(x) = worklist.pop() {
578 if let Some(frontier) = df.get(&x) {
579 for &y in frontier {
580 if !inserted_phi.contains(&y) {
581 let node_phis = phi_nodes.entry(y).or_default();
582 crate::staging_reserve::reserve_vec(
583 node_phis,
584 node_phis.len().checked_add(1).ok_or_else(|| {
585 "SSA phi node vector length overflowed usize".to_string()
586 })?,
587 "ssa phi node vector",
588 )?;
589 node_phis.push(v);
590 let next_inserted_phi_len =
591 inserted_phi.len().checked_add(1).ok_or_else(|| {
592 "SSA inserted-phi set length overflowed usize".to_string()
593 })?;
594 reserve_hash_set(
595 &mut inserted_phi,
596 next_inserted_phi_len,
597 "ssa inserted-phi set",
598 )?;
599 inserted_phi.insert(y);
600 if !in_worklist.contains(&y) {
601 let next_worklist_len =
602 worklist.len().checked_add(1).ok_or_else(|| {
603 "SSA phi placement worklist length overflowed usize".to_string()
604 })?;
605 crate::staging_reserve::reserve_vec(
606 &mut worklist,
607 next_worklist_len,
608 "ssa phi placement worklist",
609 )?;
610 worklist.push(y);
611 let next_in_worklist_len =
612 in_worklist.len().checked_add(1).ok_or_else(|| {
613 "SSA phi worklist set length overflowed usize".to_string()
614 })?;
615 reserve_hash_set(
616 &mut in_worklist,
617 next_in_worklist_len,
618 "ssa phi worklist set",
619 )?;
620 in_worklist.insert(y);
621 }
622 }
623 }
624 }
625 }
626 }
627
628 Ok(phi_nodes)
629}
630
631#[cfg(any(test, feature = "legacy-infallible"))]
633pub fn rename_variables(
634 cfg: &Cfg,
635 doms: &HashMap<u32, u32>,
636 phi_nodes: &HashMap<u32, Vec<u32>>,
637) -> SsaForm {
638 try_rename_variables(cfg, doms, phi_nodes)
639 .expect("SSA variable renaming allocation failed in legacy infallible caller")
640}
641
642pub fn try_rename_variables(
645 cfg: &Cfg,
646 doms: &HashMap<u32, u32>,
647 phi_nodes: &HashMap<u32, Vec<u32>>,
648) -> Result<SsaForm, String> {
649 let mut vars: HashSet<u32> = HashSet::new();
651 reserve_hash_set(&mut vars, cfg.blocks.len(), "ssa rename variable set")?;
652 for block in cfg.blocks.values() {
653 let next_vars_capacity = vars
654 .len()
655 .checked_add(block.defs.len())
656 .and_then(|value| value.checked_add(block.uses.len()))
657 .ok_or_else(|| "SSA rename variable set capacity overflowed usize".to_string())?;
658 reserve_hash_set(&mut vars, next_vars_capacity, "ssa rename variable set")?;
659 vars.extend(&block.defs);
660 vars.extend(&block.uses);
661 }
662
663 let mut count: HashMap<u32, u32> = HashMap::new();
664 reserve_hash_map(&mut count, vars.len(), "ssa version counter map")?;
665 let mut stack: HashMap<u32, Vec<u32>> = HashMap::new();
666 reserve_hash_map(&mut stack, vars.len(), "ssa version stack map")?;
667
668 for &v in &vars {
669 count.insert(v, 0);
670 let mut initial = crate::staging_reserve::reserved_vec(1, "ssa initial version stack")?;
671 initial.push(0);
672 stack.insert(v, initial);
673 }
674
675 let mut dom_tree: HashMap<u32, Vec<u32>> = HashMap::new();
677 reserve_hash_map(&mut dom_tree, doms.len(), "ssa dominator tree")?;
678 for (&node, &idom) in doms {
679 if node != idom {
680 let children = dom_tree.entry(idom).or_default();
681 crate::staging_reserve::reserve_vec(
682 children,
683 children.len().checked_add(1).ok_or_else(|| {
684 "SSA dominator-tree child vector length overflowed usize".to_string()
685 })?,
686 "ssa dominator-tree children",
687 )?;
688 children.push(node);
689 }
690 }
691
692 let renamed_usage_capacity = cfg.blocks.len().checked_mul(vars.len()).ok_or_else(|| {
693 "SSA renamed usage capacity overflowed usize. Fix: shard CFG blocks or variables before SSA conversion.".to_string()
694 })?;
695 let mut renamed_usages: HashMap<u32, u32> = HashMap::new();
696 reserve_hash_map(
697 &mut renamed_usages,
698 renamed_usage_capacity,
699 "ssa renamed-usage map",
700 )?;
701 let mut def_use_chains: HashMap<u32, Vec<u32>> = HashMap::new();
706 reserve_hash_map(&mut def_use_chains, vars.len(), "ssa def-use chain map")?;
707
708 fn rename_dfs(
709 u: u32,
710 cfg: &Cfg,
711 dom_tree: &HashMap<u32, Vec<u32>>,
712 phi_nodes: &HashMap<u32, Vec<u32>>,
713 count: &mut HashMap<u32, u32>,
714 stack: &mut HashMap<u32, Vec<u32>>,
715 renamed_usages: &mut HashMap<u32, u32>,
716 def_use_chains: &mut HashMap<u32, Vec<u32>>,
717 ) -> Result<(), String> {
718 if let Some(phis) = phi_nodes.get(&u) {
720 for &v in phis {
721 let next_version = {
722 let c = count.entry(v).or_insert(0);
723 *c += 1;
724 *c
725 };
726 let versions = stack.entry(v).or_default();
727 if versions.is_empty() {
728 crate::staging_reserve::reserve_vec(versions, 1, "ssa phi version stack")?;
729 versions.push(0);
730 }
731 crate::staging_reserve::reserve_vec(
732 versions,
733 versions.len().checked_add(1).ok_or_else(|| {
734 "SSA phi version stack length overflowed usize".to_string()
735 })?,
736 "ssa phi version stack",
737 )?;
738 versions.push(next_version);
739 }
740 }
741
742 if let Some(block) = cfg.blocks.get(&u) {
743 for &v in &block.defs {
744 let next_version = {
745 let c = count.entry(v).or_insert(0);
746 *c += 1;
747 *c
748 };
749 let versions = stack.entry(v).or_default();
750 if versions.is_empty() {
751 crate::staging_reserve::reserve_vec(versions, 1, "ssa def version stack")?;
752 versions.push(0);
753 }
754 crate::staging_reserve::reserve_vec(
755 versions,
756 versions.len().checked_add(1).ok_or_else(|| {
757 "SSA def version stack length overflowed usize".to_string()
758 })?,
759 "ssa def version stack",
760 )?;
761 versions.push(next_version);
762 renamed_usages.insert(synthetic_node_id(u, v)?, next_version);
764 def_use_chains.entry(next_version).or_default();
765 }
766
767 for &v in &block.uses {
768 if let Some(top) = stack.get(&v).and_then(|s| s.last()) {
769 renamed_usages.insert(synthetic_node_id(u, v)?, *top);
770 let uses = def_use_chains.entry(*top).or_default();
771 crate::staging_reserve::reserve_vec(
772 uses,
773 uses.len().checked_add(1).ok_or_else(|| {
774 "SSA def-use chain vector length overflowed usize".to_string()
775 })?,
776 "ssa def-use chain",
777 )?;
778 uses.push(u);
779 }
780 }
781 }
782
783 if let Some(children) = dom_tree.get(&u) {
784 for &child in children {
785 rename_dfs(
786 child,
787 cfg,
788 dom_tree,
789 phi_nodes,
790 count,
791 stack,
792 renamed_usages,
793 def_use_chains,
794 )?;
795 }
796 }
797
798 if let Some(phis) = phi_nodes.get(&u) {
800 for &v in phis {
801 if let Some(versions) = stack.get_mut(&v) {
802 versions.pop();
803 }
804 }
805 }
806 if let Some(block) = cfg.blocks.get(&u) {
807 for &v in &block.defs {
808 if let Some(versions) = stack.get_mut(&v) {
809 versions.pop();
810 }
811 }
812 }
813 Ok(())
814 }
815
816 rename_dfs(
817 cfg.entry,
818 cfg,
819 &dom_tree,
820 phi_nodes,
821 &mut count,
822 &mut stack,
823 &mut renamed_usages,
824 &mut def_use_chains,
825 )?;
826
827 Ok(SsaForm {
828 phi_nodes: phi_nodes.clone(),
829 renamed_usages,
830 def_use_chains,
831 })
832}
833
834#[inline]
835fn synthetic_node_id(block: u32, variable: u32) -> Result<u32, String> {
836 block
837 .checked_mul(1000)
838 .and_then(|base| base.checked_add(variable))
839 .ok_or_else(|| synthetic_node_id_overflow(block, variable))
840}
841
842#[cold]
843fn synthetic_node_id_overflow(block: u32, variable: u32) -> String {
844 let mut scratch = crate::error_format::ErrorFormatScratch::default();
845 let _ = std::fmt::Write::write_fmt(
846 &mut scratch.buf,
847 format_args!(
848 "SSA synthetic node id overflowed for block {block}, variable {variable}. Fix: provide concrete frontend node ids before SSA renaming or shard the CFG."
849 ),
850 );
851 scratch.finish()
852}
853
854#[inline]
855fn reserve_hash_map<K, V>(
856 map: &mut HashMap<K, V>,
857 capacity: usize,
858 field: &'static str,
859) -> Result<(), String>
860where
861 K: Eq + Hash,
862{
863 if map.capacity() >= capacity {
864 return Ok(());
865 }
866 map.try_reserve(capacity - map.capacity()).map_err(|error| {
867 format!(
868 "Weir SSA could not reserve {capacity} {field} entries: {error}. Fix: shard the CFG or move this SSA stage onto resident graph execution."
869 )
870 })
871}
872
873#[inline]
874fn reserve_hash_set<T>(
875 set: &mut HashSet<T>,
876 capacity: usize,
877 field: &'static str,
878) -> Result<(), String>
879where
880 T: Eq + Hash,
881{
882 if set.capacity() >= capacity {
883 return Ok(());
884 }
885 set.try_reserve(capacity - set.capacity()).map_err(|error| {
886 format!(
887 "Weir SSA could not reserve {capacity} {field} entries: {error}. Fix: shard the CFG or move this SSA stage onto resident graph execution."
888 )
889 })
890}
891
892#[cfg(test)]
893mod test_ssa {
894 use super::*;
895
896 #[test]
897 fn ssa_reference_construction_exposes_fallible_release_paths() {
898 let source = include_str!("ssa.rs");
899 let production = source
900 .split("#[cfg(test)]")
901 .next()
902 .expect("SSA production source must precede tests");
903
904 assert!(
905 production.contains("pub fn try_compute_dominators")
906 && production.contains("pub fn try_compute_dominance_frontiers")
907 && production.contains("pub fn try_rename_variables")
908 && production.contains("dense_domain::DenseU32Slots")
909 && production.contains("fn reserve_hash_map")
910 && production.contains("fn reserve_hash_set"),
911 "Fix: Weir SSA construction must expose fallible allocation-aware APIs for production callers."
912 );
913 assert!(
914 !production.contains("HashMap::with_capacity")
915 && !production.contains("HashSet::with_capacity")
916 && !production.contains("u * 1000 + v"),
917 "Fix: Weir SSA construction must not use infallible hash allocation or unchecked synthetic node-id arithmetic."
918 );
919 }
920
921 #[test]
922 fn synthetic_node_ids_reject_overflow() {
923 let error = synthetic_node_id(u32::MAX, 999)
924 .expect_err("oversized synthetic SSA node ids must fail loudly");
925 assert!(
926 error.contains("SSA synthetic node id overflowed"),
927 "{error}"
928 );
929 }
930
931 include!("tests/test_ssa.rs");
932}