use super::*;
fn next_selection_merge_suffix(blocks: &[BodyBlock]) -> usize {
let prefix = format!("{SPLIT_PREFIX}{SEL_TOKEN}");
blocks
.iter()
.filter_map(|block| block.name.strip_prefix(&prefix))
.filter_map(|suffix| suffix.parse::<usize>().ok())
.max()
.map_or(0, |max| max + 1)
}
fn loop_role_targets_with_passthroughs(
blocks: &[BodyBlock],
loop_merges: &HashMap<String, LoopMergeInfo>,
) -> HashSet<String> {
let by_name = blocks
.iter()
.map(|block| (block.name.as_str(), block))
.collect::<HashMap<_, _>>();
let mut out = HashSet::new();
for info in loop_merges.values() {
add_loop_role_target_with_passthroughs(&by_name, &mut out, &info.merge);
add_loop_role_target_with_passthroughs(&by_name, &mut out, &info.continue_target);
}
out
}
fn add_loop_role_target_with_passthroughs(
by_name: &HashMap<&str, &BodyBlock>,
out: &mut HashSet<String>,
role: &str,
) {
let mut current = role.to_string();
for _ in 0..=by_name.len() {
if !out.insert(current.clone()) {
break;
}
let Some(block) = by_name.get(current.as_str()) else {
break;
};
if !matches!(
block.role,
BlockRole::LMerge | BlockRole::ConstructTreeRoute
) {
break;
}
let successors = block_successors(block);
if successors.len() != 1 || successors[0] == current {
break;
}
current = successors[0].clone();
}
}
fn construct_tree_redirect_candidate(block: &BodyBlock) -> bool {
matches!(block.role, BlockRole::Normal | BlockRole::LMerge)
}
fn construct_tree_routes_into_natural(blocks: &[BodyBlock], natural: &str) -> HashSet<String> {
blocks
.iter()
.filter(|block| block.role == BlockRole::ConstructTreeRoute)
.filter(|block| {
block_successors(block)
.iter()
.any(|target| target == natural)
})
.map(|block| block.name.clone())
.collect()
}
fn construct_tree_preds_reclaiming_routes(
blocks: &[BodyBlock],
forest: &LoopForest,
header: &str,
natural: &str,
) -> Vec<String> {
let routes = construct_tree_routes_into_natural(blocks, natural);
if routes.is_empty() {
return Vec::new();
}
let mut preds = Vec::new();
for block in blocks {
if !construct_tree_redirect_candidate(block) || !forest.dominates(header, &block.name) {
continue;
}
let hits = block_successors(block)
.into_iter()
.any(|target| target == natural || routes.iter().any(|route| route == &target));
if hits {
preds.push(block.name.clone());
}
}
preds.sort();
preds.dedup();
preds
}
pub(in crate::native) fn forest_loop_merges(
blocks: &[BodyBlock],
converge_inloop: bool,
multi_exit_clone: bool,
) -> (Vec<BodyBlock>, HashMap<String, LoopMergeInfo>) {
let forest = analyze(blocks);
let plans = forest.structured_plan();
let mut out_blocks = blocks.to_vec();
let mut merges = HashMap::new();
let mut split_counter = 0usize;
for plan in &plans {
if plan.restructure.as_slice() == [Restructure::NoExit]
&& plan.continue_block.as_deref() == Some(plan.header.as_str())
{
if let Some((merge, continue_target)) =
synth_noexit_self_latch(&mut out_blocks, &plan.header, &mut split_counter)
{
merges.insert(
plan.header.clone(),
LoopMergeInfo {
merge,
continue_target,
},
);
}
continue;
}
let (Some(continue_target), Some(merge_block)) =
(plan.continue_block.clone(), plan.merge_block.clone())
else {
continue;
};
if crate::env_vars::flm_why() {
let collides = merge_collides_with_outer_selection(
&out_blocks,
&plan.header,
&merge_block,
converge_inloop,
);
eprintln!(
"[flm-why] header={} restructure={:?} merge={} collides={} has_phi={}",
plan.header,
plan.restructure,
merge_block,
collides,
block_has_phi(&out_blocks, &merge_block),
);
}
if plan.restructure.is_empty() {
let merge_block = if merge_collides_with_outer_selection(
&out_blocks,
&plan.header,
&merge_block,
converge_inloop,
) {
let det_forest = analyze(&out_blocks);
let split = if block_has_phi(&out_blocks, &merge_block) {
split_phi_overlap(
&mut out_blocks,
&det_forest,
&plan.header,
&merge_block,
&mut split_counter,
)
} else {
split_no_phi_overlap(
&mut out_blocks,
&det_forest,
&plan.header,
&merge_block,
&mut split_counter,
)
};
split.unwrap_or(merge_block)
} else {
merge_block
};
let continue_target = synth_dowhile_continue(
&mut out_blocks,
&plan.header,
&continue_target,
&merge_block,
&mut split_counter,
)
.unwrap_or(continue_target);
merges.insert(
plan.header.clone(),
LoopMergeInfo {
merge: merge_block,
continue_target,
},
);
continue;
}
if plan.restructure.as_slice() == [Restructure::MultipleExits] {
let exits = forest
.loop_for_header(&plan.header)
.map(|l| l.exits.clone())
.unwrap_or_default();
if let Some(new_merge) = synth_multi_exit_merge(
&mut out_blocks,
&forest,
&plan.header,
&exits,
&mut split_counter,
) {
if multi_exit_clone {
privatize_dispatch_shared_exits(
&mut out_blocks,
&new_merge,
&mut split_counter,
);
}
let continue_target = synth_dowhile_continue(
&mut out_blocks,
&plan.header,
&continue_target,
&new_merge,
&mut split_counter,
)
.unwrap_or(continue_target);
merges.insert(
plan.header.clone(),
LoopMergeInfo {
merge: new_merge,
continue_target,
},
);
}
continue;
}
if plan.restructure.as_slice() == [Restructure::MultipleLatches] {
let latches = forest
.loop_for_header(&plan.header)
.map(|l| l.latches.clone())
.unwrap_or_default();
if let Some(new_latch) = synth_multi_latch_continue(
&mut out_blocks,
&plan.header,
&latches,
&mut split_counter,
) {
let continue_target = synth_dowhile_continue(
&mut out_blocks,
&plan.header,
&new_latch,
&merge_block,
&mut split_counter,
)
.unwrap_or(new_latch);
merges.insert(
plan.header.clone(),
LoopMergeInfo {
merge: merge_block,
continue_target,
},
);
}
continue;
}
if plan.restructure.as_slice() != [Restructure::MergeIsEnclosingContinue] {
continue;
}
let split = if block_has_phi(&out_blocks, &merge_block) {
split_phi_overlap(
&mut out_blocks,
&forest,
&plan.header,
&merge_block,
&mut split_counter,
)
} else {
split_no_phi_overlap(
&mut out_blocks,
&forest,
&plan.header,
&merge_block,
&mut split_counter,
)
};
if let Some(new_merge) = split {
let continue_target = synth_dowhile_continue(
&mut out_blocks,
&plan.header,
&continue_target,
&new_merge,
&mut split_counter,
)
.unwrap_or(continue_target);
merges.insert(
plan.header.clone(),
LoopMergeInfo {
merge: new_merge,
continue_target,
},
);
}
}
let mut header_infos: Vec<(String, String, String)> = merges
.iter()
.map(|(h, i)| (h.clone(), i.merge.clone(), i.continue_target.clone()))
.collect();
header_infos.sort();
for (h, m, c) in header_infos {
split_loop_header_selection(&mut out_blocks, &h, &m, &c, &mut split_counter);
split_loop_header_switch(&mut out_blocks, &h, &m, &c, &mut split_counter);
}
(out_blocks, merges)
}
pub(in crate::native) fn synth_noexit_self_latch(
blocks: &mut Vec<BodyBlock>,
header: &str,
counter: &mut usize,
) -> Option<(String, String)> {
let header_idx = blocks.iter().position(|b| b.name == header)?;
let src_typed = blocks[header_idx].typed.clone()?;
if !matches!(&src_typed.terminator, crate::native::tir::TirTerminator::Br(l) if l == header) {
return None;
}
let id = *counter;
*counter += 1;
let body = format!("%metal2vulkan.noexit.body.{id}");
let continue_target = format!("%metal2vulkan.noexit.cont.{id}");
let merge = format!("%metal2vulkan.noexit.merge.{id}");
let header_name = blocks[header_idx].name.clone();
let phi_count = src_typed.insts.iter().take_while(|i| i.is_phi()).count();
let body_typed = crate::native::tir::lower_block_carrier_from_suffix(
&body,
&src_typed,
phi_count,
&format!("br label {continue_target}"),
);
blocks[header_idx].typed = crate::native::tir::lower_block_carrier_prefix(
&header_name,
&src_typed,
phi_count,
&format!("br label {body}"),
)
.map(|mut h| {
h.rewrite_phi_predecessor(header, &continue_target);
h
});
blocks.insert(
header_idx + 1,
BodyBlock {
name: body,
role: BlockRole::Normal,
typed: body_typed,
},
);
let cont_typed = crate::native::tir::lower_block_carrier(
&continue_target,
&[format!("br label {header}")],
&std::collections::HashMap::new(),
);
blocks.insert(
header_idx + 2,
BodyBlock {
name: continue_target.clone(),
role: BlockRole::Normal,
typed: cont_typed,
},
);
let merge_typed = crate::native::tir::lower_block_carrier(
&merge,
&["unreachable".to_string()],
&std::collections::HashMap::new(),
);
blocks.push(BodyBlock {
name: merge.clone(),
role: BlockRole::Normal,
typed: merge_typed,
});
Some((merge, continue_target))
}
pub(in crate::native) fn unique_selection_merges(
blocks: &[BodyBlock],
loop_merges: &HashMap<String, LoopMergeInfo>,
break_aware: bool,
) -> (
Vec<BodyBlock>,
HashMap<(String, String), String>,
HashMap<String, String>,
) {
let (blocks, branch, _branch_by_header, switch) =
unique_selection_merges_with_loop_exit(blocks, loop_merges, break_aware, false);
(blocks, branch, switch)
}
pub(in crate::native) fn unique_selection_merges_with_loop_exit(
blocks: &[BodyBlock],
loop_merges: &HashMap<String, LoopMergeInfo>,
break_aware: bool,
loop_exit_selection: bool,
) -> (
Vec<BodyBlock>,
HashMap<(String, String), String>,
HashMap<String, String>,
HashMap<String, String>,
) {
unique_selection_merges_with_loop_exit_and_forced(
blocks,
loop_merges,
break_aware,
loop_exit_selection,
&HashMap::new(),
)
}
pub(in crate::native) fn unique_selection_merges_with_loop_exit_and_forced(
blocks: &[BodyBlock],
loop_merges: &HashMap<String, LoopMergeInfo>,
break_aware: bool,
loop_exit_selection: bool,
forced_terminal_merges: &HashMap<String, String>,
) -> (
Vec<BodyBlock>,
HashMap<(String, String), String>,
HashMap<String, String>,
HashMap<String, String>,
) {
unique_selection_merges_with_loop_exit_and_forced_inner(
blocks,
loop_merges,
break_aware,
loop_exit_selection,
forced_terminal_merges,
false,
)
}
pub(in crate::native) fn unique_selection_merges_with_construct_tree_ownership(
blocks: &[BodyBlock],
loop_merges: &HashMap<String, LoopMergeInfo>,
break_aware: bool,
loop_exit_selection: bool,
forced_terminal_merges: &HashMap<String, String>,
) -> (
Vec<BodyBlock>,
HashMap<(String, String), String>,
HashMap<String, String>,
HashMap<String, String>,
) {
unique_selection_merges_with_loop_exit_and_forced_inner(
blocks,
loop_merges,
break_aware,
loop_exit_selection,
forced_terminal_merges,
true,
)
}
#[allow(clippy::too_many_arguments)]
fn unique_selection_merges_with_loop_exit_and_forced_inner(
blocks: &[BodyBlock],
loop_merges: &HashMap<String, LoopMergeInfo>,
break_aware: bool,
loop_exit_selection: bool,
forced_terminal_merges: &HashMap<String, String>,
construct_tree_owned: bool,
) -> (
Vec<BodyBlock>,
HashMap<(String, String), String>,
HashMap<String, String>,
HashMap<String, String>,
) {
let forest = analyze(blocks);
let mut sel = if break_aware {
break_aware_selection_merges(blocks, &forest, loop_merges)
} else {
selection_merges(blocks, &forest)
};
if loop_exit_selection {
refine_loop_exit_selection_merges(blocks, &forest, loop_merges, &mut sel);
}
let loop_headers: HashSet<&str> = forest.loops.iter().map(|l| l.header.as_str()).collect();
let loop_roles = loop_role_targets_with_passthroughs(blocks, loop_merges);
let mut claims: HashMap<&str, usize> = HashMap::new();
for m in sel.values() {
*claims.entry(m.as_str()).or_default() += 1;
}
let mut construct_tree_selection_merges = sel.clone();
construct_tree_selection_merges.extend(forced_terminal_merges.clone());
let mut out = blocks.to_vec();
let mut branch = HashMap::new();
let mut switch = HashMap::new();
let mut header_merges: HashMap<String, String> = HashMap::new();
let mut counter = if construct_tree_owned {
next_selection_merge_suffix(&out)
} else {
0usize
};
let mut break_continue_blocks: HashSet<String> = HashSet::new();
for b in blocks {
if loop_headers.contains(b.name.as_str()) {
continue;
}
let Some((t, f)) = conditional_branch_targets(b) else {
continue;
};
if let Some(cont) = loop_break_continue_merge(&forest, loop_merges, &b.name, &t, &f) {
header_merges.insert(b.name.clone(), cont.clone());
if !loop_exit_selection {
branch.insert((t, f), cont);
}
break_continue_blocks.insert(b.name.clone());
}
}
let depth = |name: &str| {
let mut d = 0usize;
let mut cur = name;
while let Some(p) = forest.idom(cur) {
d += 1;
cur = p;
}
d
};
let mut headers: Vec<&BodyBlock> = blocks
.iter()
.filter(|b| {
!loop_headers.contains(b.name.as_str())
&& (sel.contains_key(&b.name) || forced_terminal_merges.contains_key(&b.name))
&& !break_continue_blocks.contains(&b.name)
})
.collect();
headers.sort_by_key(|b| std::cmp::Reverse(depth(&b.name)));
let mut cur_forest = analyze(&out);
#[derive(Clone, Debug)]
struct CtOwnedPred {
block: String,
origins: Vec<String>,
}
let mut ct_owned_preds: HashMap<(String, String), Vec<CtOwnedPred>> = HashMap::new();
for b in headers.iter().copied() {
if let Some(merge) = forced_terminal_merges.get(&b.name).cloned() {
header_merges.insert(b.name.clone(), merge.clone());
if !loop_exit_selection {
let cur_block = out.iter().find(|x| x.name == b.name).unwrap_or(b);
if let Some((true_target, false_target)) = conditional_branch_targets(cur_block) {
branch.insert((true_target, false_target), merge);
}
}
continue;
}
let Some(natural) = sel.get(&b.name).cloned() else {
continue;
};
if construct_tree_owned {
if let Some((true_target, false_target)) = conditional_branch_targets(b) {
if bare_loop_exit_branch(&forest, loop_merges, &b.name, &true_target, &false_target)
{
continue;
}
if let Some(exit_target) = enclosing_selection_region_exit_target(
blocks,
&forest,
&construct_tree_selection_merges,
&b.name,
&true_target,
&false_target,
Some(&natural),
) {
let synth = if block_has_phi(&out, &exit_target) {
synth_unique_selection_merge_phi(
&mut out,
&forest,
&b.name,
&exit_target,
&mut counter,
)
} else {
synth_unique_selection_merge(
&mut out,
&forest,
&b.name,
&exit_target,
&mut counter,
)
};
if let Some(merge) = synth {
header_merges.insert(b.name.clone(), merge);
continue;
}
}
}
}
let natural_has_phi = block_has_phi(&out, &natural);
let natural_is_synthetic_merge = construct_tree_owned
&& !natural_has_phi
&& out
.iter()
.find(|block| block.name == natural)
.is_some_and(|block| block.role == BlockRole::LMerge);
let dominance_forest = if construct_tree_owned {
&forest
} else {
&cur_forest
};
let collides = claims.get(natural.as_str()).copied().unwrap_or(0) > 1
|| loop_roles.contains(&natural)
|| natural_is_synthetic_merge
|| !dominance_forest.dominates(&b.name, &natural);
let merge = if collides {
let source_owned_preds = if construct_tree_owned {
out.iter()
.filter(|candidate| {
construct_tree_redirect_candidate(candidate)
&& forest.dominates(&b.name, &candidate.name)
&& block_successors(candidate)
.iter()
.any(|target| target == &natural)
})
.map(|candidate| candidate.name.clone())
.collect::<Vec<_>>()
} else {
Vec::new()
};
let route_reclaimed_preds = if construct_tree_owned && source_owned_preds.is_empty() {
construct_tree_preds_reclaiming_routes(&out, &forest, &b.name, &natural)
} else {
Vec::new()
};
let carried_preds = if construct_tree_owned {
ct_owned_preds
.remove(&(b.name.clone(), natural.clone()))
.unwrap_or_default()
} else {
Vec::new()
};
let current_owned_preds = Vec::new();
let mut explicit_no_phi_preds = source_owned_preds.clone();
explicit_no_phi_preds.extend(route_reclaimed_preds.iter().cloned());
explicit_no_phi_preds.extend(current_owned_preds);
explicit_no_phi_preds.extend(carried_preds.iter().map(|pred| pred.block.clone()));
explicit_no_phi_preds.sort();
explicit_no_phi_preds.dedup();
let mut propagated_origins = source_owned_preds.clone();
propagated_origins.extend(route_reclaimed_preds.iter().cloned());
for pred in &carried_preds {
propagated_origins.extend(pred.origins.iter().cloned());
}
propagated_origins.sort();
propagated_origins.dedup();
let routes_into_natural = if construct_tree_owned && !route_reclaimed_preds.is_empty() {
construct_tree_routes_into_natural(&out, &natural)
} else {
HashSet::new()
};
let synth = if natural_has_phi {
if construct_tree_owned {
synth_unique_selection_merge_phi(
&mut out,
&forest,
&b.name,
&natural,
&mut counter,
)
} else {
synth_unique_selection_merge_phi(
&mut out,
&cur_forest,
&b.name,
&natural,
&mut counter,
)
}
} else if construct_tree_owned {
synth_unique_selection_merge_no_phi_explicit(
&mut out,
&explicit_no_phi_preds,
&natural,
&routes_into_natural,
&mut counter,
)
} else {
let synth = synth_unique_selection_merge(
&mut out,
&cur_forest,
&b.name,
&natural,
&mut counter,
);
if synth.is_none() && construct_tree_owned {
synth_unique_selection_merge(&mut out, &forest, &b.name, &natural, &mut counter)
} else {
synth
}
};
match synth {
Some(s) => {
if !construct_tree_owned {
cur_forest = analyze(&out);
}
if construct_tree_owned && !propagated_origins.is_empty() {
for target in &headers {
if target.name == b.name {
continue;
}
if sel.get(&target.name) != Some(&natural) {
continue;
}
if propagated_origins
.iter()
.all(|origin| forest.dominates(&target.name, origin))
{
ct_owned_preds
.entry((target.name.clone(), natural.clone()))
.or_default()
.push(CtOwnedPred {
block: s.clone(),
origins: propagated_origins.clone(),
});
}
}
}
s
}
None => continue,
}
} else {
natural
};
header_merges.insert(b.name.clone(), merge.clone());
if !loop_exit_selection {
let cur_block = out.iter().find(|x| x.name == b.name).unwrap_or(b);
if is_switch_block(cur_block) {
switch.insert(b.name.clone(), merge);
} else if let Some((true_target, false_target)) = conditional_branch_targets(cur_block)
{
branch.insert((true_target, false_target), merge);
}
}
}
if construct_tree_owned {
repair_construct_tree_nondominated_selection_merges(
&mut out,
loop_merges,
&mut header_merges,
&mut counter,
);
repair_construct_tree_enclosing_selection_region_escapes(
&mut out,
loop_merges,
&mut header_merges,
&mut counter,
);
repair_construct_tree_passthrough_selection_merges(&out, &mut header_merges);
}
let rekey_all =
loop_exit_selection || !forced_terminal_merges.is_empty() || construct_tree_owned;
if rekey_all || !break_continue_blocks.is_empty() {
for block in &out {
if !rekey_all && !break_continue_blocks.contains(&block.name) {
continue;
}
let Some(merge) = header_merges.get(&block.name).cloned() else {
continue;
};
if is_switch_block(block) {
switch.insert(block.name.clone(), merge);
} else if let Some((true_target, false_target)) = conditional_branch_targets(block) {
branch.insert((true_target, false_target), merge);
}
}
}
(out, branch, header_merges, switch)
}
pub(in crate::native) fn repair_construct_tree_nondominated_selection_merges(
blocks: &mut Vec<BodyBlock>,
loop_merges: &HashMap<String, LoopMergeInfo>,
header_merges: &mut HashMap<String, String>,
counter: &mut usize,
) {
for _ in 0..8 {
let forest = analyze(blocks);
let mut merge_of = HashMap::new();
for (header, info) in loop_merges {
merge_of.insert(header.clone(), info.merge.clone());
}
merge_of.extend(
header_merges
.iter()
.map(|(header, merge)| (header.clone(), merge.clone())),
);
let order = structured_order(blocks, &forest, |header| merge_of.get(header).cloned());
let rank = order
.iter()
.enumerate()
.map(|(index, name)| (name.as_str(), index))
.collect::<HashMap<_, _>>();
let mut headers = blocks
.iter()
.filter_map(|block| Some((block.name.clone(), header_merges.get(&block.name)?.clone())))
.collect::<Vec<_>>();
headers.sort_by_key(|(header, _)| rank.get(header.as_str()).copied().unwrap_or(usize::MAX));
let mut repaired = false;
for (header, merge) in headers {
if forest.dominates(&header, &merge) {
continue;
}
let Some(block) = blocks.iter().find(|block| block.name == header) else {
continue;
};
let header_targets_merge = block_successors(block)
.iter()
.any(|target| target == &merge);
let route_reclaim_available = !header_targets_merge
&& !construct_tree_preds_reclaiming_routes(blocks, &forest, &header, &merge)
.is_empty();
if !header_targets_merge && !route_reclaim_available {
continue;
}
if let Some((t, f)) = conditional_branch_targets(block) {
if bare_loop_exit_branch_with_passthroughs(
blocks,
&forest,
loop_merges,
&header,
&t,
&f,
) {
continue;
}
}
if block_has_phi(blocks, &merge) {
continue;
}
let mut repair_preds = blocks
.iter()
.filter(|candidate| {
construct_tree_redirect_candidate(candidate)
&& forest.dominates(&header, &candidate.name)
&& block_successors(candidate)
.iter()
.any(|target| target == &merge)
})
.map(|candidate| candidate.name.clone())
.collect::<Vec<_>>();
let routes_into_merge = if repair_preds.is_empty() {
let reclaimed =
construct_tree_preds_reclaiming_routes(blocks, &forest, &header, &merge);
repair_preds.extend(reclaimed);
construct_tree_routes_into_natural(blocks, &merge)
} else {
HashSet::new()
};
repair_preds.sort();
repair_preds.dedup();
let split = synth_unique_selection_merge_no_phi_explicit(
blocks,
&repair_preds,
&merge,
&routes_into_merge,
counter,
);
if let Some(private_merge) = split {
header_merges.insert(header, private_merge);
repaired = true;
break;
}
}
if !repaired {
break;
}
}
}
pub(in crate::native) fn repair_construct_tree_passthrough_selection_merges(
blocks: &[BodyBlock],
header_merges: &mut HashMap<String, String>,
) {
let forest = analyze(blocks);
let by_name = blocks
.iter()
.map(|block| (block.name.as_str(), block))
.collect::<HashMap<_, _>>();
for _ in 0..blocks.len() {
let mut changed = false;
let assignments = header_merges
.iter()
.map(|(header, merge)| (header.clone(), merge.clone()))
.collect::<Vec<_>>();
for (header, merge) in assignments {
let Some(merge_block) = by_name.get(merge.as_str()) else {
continue;
};
if merge_block.role != BlockRole::LMerge {
continue;
}
let successors = block_successors(merge_block);
let [successor] = successors.as_slice() else {
continue;
};
if !forest.dominates(&header, successor) {
continue;
}
let bypasses_merge = blocks.iter().any(|candidate| {
candidate.name != merge
&& forest.dominates(&header, &candidate.name)
&& block_successors(candidate)
.iter()
.any(|target| target == successor)
});
if bypasses_merge {
header_merges.insert(header, successor.clone());
changed = true;
}
}
if !changed {
break;
}
}
}
fn synth_unique_selection_merge_no_phi_explicit(
blocks: &mut Vec<BodyBlock>,
preds: &[String],
natural: &str,
routes_into_natural: &HashSet<String>,
counter: &mut usize,
) -> Option<String> {
let pred_set: HashSet<&str> = preds.iter().map(String::as_str).collect();
let mut redirect: Vec<(String, String)> = Vec::new();
for block in blocks.iter() {
if !pred_set.contains(block.name.as_str()) {
continue;
}
for target in block_successors(block) {
if target == natural || routes_into_natural.contains(&target) {
redirect.push((block.name.clone(), target));
}
}
}
if redirect.is_empty() {
return None;
}
let new_name = format!("{SPLIT_PREFIX}{SEL_TOKEN}{counter}");
*counter += 1;
for (pred, old_target) in &redirect {
if let Some(block) = blocks.iter_mut().find(|block| &block.name == pred) {
if let Some(typed) = &mut block.typed {
typed.redirect_successor(old_target, &new_name);
}
}
}
let at = blocks
.iter()
.position(|block| block.name == natural)
.unwrap_or(blocks.len());
blocks.insert(
at,
synthetic_block(
new_name.clone(),
vec![format!("br label {natural}")],
role_for_name(&new_name),
),
);
Some(new_name)
}
#[cfg(test)]
mod tests {
use super::*;
fn block(name: &str) -> BodyBlock {
BodyBlock {
name: name.to_string(),
role: role_for_name(name),
typed: crate::native::tir::lower_block_carrier(
name,
&["ret void".to_string()],
&HashMap::new(),
),
}
}
#[test]
fn construct_tree_selection_counter_skips_existing_sel_suffixes() {
let blocks = vec![
block("%entry"),
block("%metal2vulkan.lmerge.sel0"),
block("%metal2vulkan.lmerge.sel12"),
block("%metal2vulkan.lmerge.selx"),
block("%metal2vulkan.lmerge.99"),
];
assert_eq!(next_selection_merge_suffix(&blocks), 13);
}
}