use super::*;
pub(in crate::native) fn split_multi_exit_critical_edges(
blocks: &mut Vec<BodyBlock>,
forest: &LoopForest,
header: &str,
exits: &[String],
counter: &mut usize,
) -> Option<Vec<(String, usize)>> {
if exits.len() != 2 {
return None;
}
let body: HashSet<&str> = forest
.loop_for_header(header)?
.body
.iter()
.map(String::as_str)
.collect();
let mut splits: Vec<(String, Vec<usize>)> = Vec::new();
for block in blocks.iter().filter(|b| body.contains(b.name.as_str())) {
let successors = block_successors(block);
let hit: Vec<usize> = exits
.iter()
.enumerate()
.filter_map(|(index, exit)| {
successors
.iter()
.any(|target| target == exit)
.then_some(index)
})
.collect();
if hit.len() < 2 {
continue;
}
splits.push((block.name.clone(), hit));
}
if splits.is_empty() {
return Some(Vec::new());
}
let mut edge_blocks = Vec::new();
let mut edge_preds = Vec::new();
for (source, targets) in splits {
let mut edges = Vec::with_capacity(targets.len());
for index in targets {
let edge = format!("{EXIT_EDGE_PREFIX}{counter}");
*counter += 1;
edges.push((index, edge));
}
let source_block = blocks.iter_mut().find(|b| b.name == source)?;
if let Some(t) = &mut source_block.typed {
for (index, edge) in &edges {
t.redirect_successor(&exits[*index], edge);
}
}
for (index, edge) in &edges {
let exit = blocks.iter_mut().find(|b| b.name == exits[*index])?;
if let Some(t) = &mut exit.typed {
t.rewrite_phi_predecessor(&source, edge);
}
let edge_lines = vec![format!("br label {}", exits[*index])];
edge_blocks.push(BodyBlock {
typed: crate::native::tir::lower_block_carrier(
edge,
&edge_lines,
&std::collections::HashMap::new(),
),
name: edge.clone(),
role: role_for_name(edge),
});
edge_preds.push((edge.clone(), *index));
}
}
blocks.extend(edge_blocks);
Some(edge_preds)
}
pub(in crate::native) fn synth_multi_exit_merge(
blocks: &mut Vec<BodyBlock>,
forest: &LoopForest,
header: &str,
exits: &[String],
counter: &mut usize,
) -> Option<String> {
if exits.len() != 2 {
return None;
}
let mut exits = exits.to_vec();
exits.sort();
let exits = &exits[..];
let critical_edge_preds =
split_multi_exit_critical_edges(blocks, forest, header, exits, counter)?;
let body: HashSet<&str> = forest
.loop_for_header(header)?
.body
.iter()
.map(String::as_str)
.collect();
let mut preds: Vec<(String, usize)> = Vec::new();
let critical_edge_set: HashSet<&str> = critical_edge_preds
.iter()
.map(|(name, _)| name.as_str())
.collect();
for b in blocks.iter() {
if !body.contains(b.name.as_str()) && !critical_edge_set.contains(b.name.as_str()) {
continue;
}
let mut hit: Option<usize> = None;
for succ in block_successors(b) {
if let Some(idx) = exits.iter().position(|e| e == &succ) {
match hit {
Some(prev) if prev != idx => return None,
_ => hit = Some(idx),
}
}
}
if let Some(idx) = hit {
preds.push((b.name.clone(), idx));
}
}
if preds.is_empty() {
return None;
}
let merge = format!("{SPLIT_PREFIX}{counter}");
*counter += 1;
let sel = format!("{EXIT_SEL_PREFIX}{counter}");
*counter += 1;
for b in blocks.iter_mut() {
if let Some((_, idx)) = preds.iter().find(|(n, _)| n == &b.name) {
if let Some(t) = &mut b.typed {
t.redirect_successor(&exits[*idx], &merge);
}
}
}
let dispatch_targets = [exits[0].clone(), exits[1].clone()];
let exit_edge_pred = [merge.clone(), merge.clone()];
type TypedIncomings = Vec<(crate::native::ir::LlValue, String)>;
let mut typed_value_phis: Vec<(String, crate::native::ir::LlType, TypedIncomings)> = Vec::new();
for (ei, e) in exits.iter().enumerate() {
let Some(exit_idx) = blocks.iter().position(|b| &b.name == e) else {
continue;
};
let edge_pred = exit_edge_pred[ei].clone();
let mut exit_typed_merges: Vec<(String, TypedIncomings)> = Vec::new();
if let Some(t) = &blocks[exit_idx].typed {
for inst in &t.insts {
let (Some(dst), Some((cty, inc))) =
(inst.result.clone(), inst.phi_incoming.clone())
else {
continue;
};
let is_this_exit_pred =
|p: &str| preds.iter().any(|(name, idx)| name == p && *idx == ei);
let (typed_red, mut kept_plus): (Vec<_>, Vec<_>) =
inc.into_iter().partition(|(_, p)| is_this_exit_pred(p));
if typed_red.is_empty() {
continue;
}
let merged = format!("%metal2vulkan.exitphi.{counter}");
*counter += 1;
let merged_typed: TypedIncomings = preds
.iter()
.map(|(pname, pidx)| {
let v = if *pidx == ei {
typed_red
.iter()
.find(|(_, p)| p == pname)
.map(|(v, _)| v.clone())
.unwrap_or(crate::native::ir::LlValue::Undef)
} else {
crate::native::ir::LlValue::Undef
};
(v, pname.clone())
})
.collect();
kept_plus.push((
crate::native::ir::LlValue::Local(merged.clone()),
edge_pred.clone(),
));
exit_typed_merges.push((dst, kept_plus));
typed_value_phis.push((merged, cty, merged_typed));
}
}
if let Some(t) = &mut blocks[exit_idx].typed {
for (dst, kept_plus) in &exit_typed_merges {
t.set_phi_incomings(dst, kept_plus);
}
}
}
let selector_typed: TypedIncomings = preds
.iter()
.map(|(name, idx)| (crate::native::ir::LlValue::Bool(*idx == 0), name.clone()))
.collect();
let branch_line = format!(
"br i1 {sel}, label {}, label {}",
dispatch_targets[0], dispatch_targets[1]
);
let mut blk = crate::native::tir::lower_block_carrier(
&merge,
&[branch_line],
&std::collections::HashMap::new(),
)?;
for (mname, ty, merged_typed) in &typed_value_phis {
blk.push_value_phi(mname, ty, merged_typed);
}
blk.push_value_phi(&sel, &crate::native::ir::LlType::Int(1), &selector_typed);
let merge_block = BodyBlock {
name: merge.clone(),
role: role_for_name(&merge),
typed: Some(blk),
};
let insert_at = blocks
.iter()
.position(|b| b.name == exits[0] || b.name == exits[1])
.unwrap_or(blocks.len());
blocks.insert(insert_at, merge_block);
Some(merge)
}
pub(in crate::native) fn synth_multi_latch_continue(
blocks: &mut Vec<BodyBlock>,
header: &str,
latches: &[String],
counter: &mut usize,
) -> Option<String> {
if latches.len() < 2 {
return None;
}
let mut latches = latches.to_vec();
latches.sort();
latches.dedup();
let latch_set: HashSet<&str> = latches.iter().map(String::as_str).collect();
for l in &latches {
let b = blocks.iter().find(|b| &b.name == l)?;
if !block_successors(b).iter().any(|s| s == header) {
return None;
}
}
let hidx = blocks.iter().position(|b| b.name == header)?;
let new_latch = format!("{SPLIT_PREFIX}{counter}");
*counter += 1;
type TypedIncomings = Vec<(crate::native::ir::LlValue, String)>;
let mut typed_value_phis: Vec<(String, crate::native::ir::LlType, TypedIncomings)> = Vec::new();
let mut header_typed_merges: Vec<(String, TypedIncomings)> = Vec::new();
if let Some(t) = &blocks[hidx].typed {
for inst in &t.insts {
let (Some(dst), Some((cty, inc))) = (inst.result.clone(), inst.phi_incoming.clone())
else {
continue;
};
let (typed_red, mut kept_plus): (Vec<_>, Vec<_>) = inc
.into_iter()
.partition(|(_, p)| latch_set.contains(p.as_str()));
if typed_red.is_empty() {
continue;
}
let merged = format!("%metal2vulkan.latchphi.{counter}");
*counter += 1;
let merged_typed: TypedIncomings = latches
.iter()
.map(|lname| {
let v = typed_red
.iter()
.find(|(_, p)| p == lname)
.map(|(v, _)| v.clone())
.unwrap_or(crate::native::ir::LlValue::Undef);
(v, lname.clone())
})
.collect();
kept_plus.push((
crate::native::ir::LlValue::Local(merged.clone()),
new_latch.clone(),
));
header_typed_merges.push((dst, kept_plus));
typed_value_phis.push((merged, cty, merged_typed));
}
}
if let Some(t) = &mut blocks[hidx].typed {
for (dst, kept_plus) in &header_typed_merges {
t.set_phi_incomings(dst, kept_plus);
}
}
for l in &latches {
if let Some(b) = blocks.iter_mut().find(|b| &b.name == l) {
if let Some(t) = &mut b.typed {
t.redirect_successor(header, &new_latch);
}
}
}
let insert_at = hidx.max(1);
let latch_block = {
let mut blk = crate::native::tir::lower_block_carrier(
&new_latch,
&[format!("br label {header}")],
&std::collections::HashMap::new(),
)?;
for (merged, ty, merged_typed) in &typed_value_phis {
blk.push_value_phi(merged, ty, merged_typed);
}
BodyBlock {
name: new_latch.clone(),
role: role_for_name(&new_latch),
typed: Some(blk),
}
};
blocks.insert(insert_at, latch_block);
Some(new_latch)
}