use super::*;
pub(in crate::native) const TERMINAL_EXIT_SELECTION_MAX_BLOCKS: usize = 300;
pub(in crate::native) const MAX_TERMINAL_EXIT_SELECTION_ROUNDS: usize = 16;
pub(in crate::native) const TERMINAL_EXIT_COUNTER_START: usize = 3_000_000;
pub(in crate::native) struct TerminalExitSelectionPlan {
pub(in crate::native) blocks: Vec<BodyBlock>,
pub(in crate::native) merges: HashMap<String, String>,
}
pub(in crate::native) fn privatize_single_loop_return_exit(
blocks: &[BodyBlock],
) -> Option<Vec<BodyBlock>> {
if blocks.len() > TERMINAL_EXIT_SELECTION_MAX_BLOCKS {
return None;
}
let forest = analyze(blocks);
let [loop_info] = forest.loops.as_slice() else {
return None;
};
if loop_info.latches.len() != 1 || !(1..=2).contains(&loop_info.exits.len()) {
return None;
}
let return_exits: Vec<&String> = loop_info
.exits
.iter()
.filter(|exit| {
blocks
.iter()
.find(|block| block.name == exit.as_str())
.is_some_and(is_bare_ret_void)
})
.collect();
let [exit] = return_exits.as_slice() else {
return None;
};
let exit = exit.as_str();
if loop_info.exits.len() == 2
&& !loop_info
.exits
.iter()
.any(|candidate| block_ends_in_unreachable(blocks, candidate))
{
return None;
}
let exit_block = blocks.iter().find(|block| block.name == exit)?;
debug_assert!(exit_block
.typed
.as_ref()
.is_some_and(|t| t.insts.is_empty()));
let loop_nodes: HashSet<&str> = loop_info.body.iter().map(String::as_str).collect();
let mut loop_predecessors = Vec::new();
let mut has_outer_predecessor = false;
for block in blocks {
if !block_successors(block).iter().any(|target| target == exit) {
continue;
}
if loop_nodes.contains(block.name.as_str()) {
loop_predecessors.push(block.name.clone());
} else {
has_outer_predecessor = true;
}
}
if loop_predecessors.is_empty() || !has_outer_predecessor {
return None;
}
let names: HashSet<&str> = blocks.iter().map(|block| block.name.as_str()).collect();
let mut id = TERMINAL_EXIT_COUNTER_START;
let private_return = loop {
let candidate = format!("{SPLIT_PREFIX}{TLOOPRET_TOKEN}{id}");
if !names.contains(candidate.as_str()) {
break candidate;
}
id += 1;
};
let mut out = blocks.to_vec();
for predecessor in &loop_predecessors {
let idx = out.iter().position(|block| block.name == *predecessor)?;
if !block_successors(&out[idx]).iter().any(|s| s == exit) {
return None;
}
out[idx]
.typed
.as_mut()?
.redirect_successor(exit, &private_return);
}
let role = role_for_name(&private_return);
out.push(synthetic_block(
private_return,
vec!["ret void".to_string()],
role,
));
Some(out)
}
pub(in crate::native) fn terminal_exit_selection_merges(
blocks: &[BodyBlock],
) -> Option<TerminalExitSelectionPlan> {
if blocks.len() > TERMINAL_EXIT_SELECTION_MAX_BLOCKS {
return None;
}
let mut out = blocks.to_vec();
let mut merges = HashMap::new();
let mut counter = TERMINAL_EXIT_COUNTER_START;
let mut changed = false;
for _ in 0..MAX_TERMINAL_EXIT_SELECTION_ROUNDS {
let forest = analyze(&out);
let loop_nodes: HashSet<&str> = forest
.loops
.iter()
.flat_map(|loop_info| loop_info.body.iter().map(String::as_str))
.collect();
let depth = |name: &str| {
let mut depth = 0usize;
let mut current = name;
while let Some(parent) = forest.idom(current) {
depth += 1;
current = parent;
}
depth
};
let mut headers: Vec<String> = out
.iter()
.filter(|block| !merges.contains_key(&block.name))
.filter(|block| !loop_nodes.contains(block.name.as_str()))
.filter(|block| conditional_branch_targets(block).is_some())
.map(|block| block.name.clone())
.collect();
headers.sort_by(|left, right| depth(right).cmp(&depth(left)).then(left.cmp(right)));
let mut applied = false;
'headers: for header in headers {
if let Some(terminal) = terminal_exit_continuation(&out, &forest, &header) {
let mut candidate = out.clone();
let mut candidate_counter = counter;
let Some(merge) = synth_terminal_selection_merge(
&mut candidate,
&forest,
&header,
&terminal.continuation,
&mut candidate_counter,
) else {
continue;
};
let Some(_) = synth_terminal_return_clone(
&mut candidate,
&header,
&terminal.exit_arm,
&terminal.return_arm,
&mut candidate_counter,
) else {
continue;
};
out = candidate;
counter = candidate_counter;
merges.insert(header, merge);
changed = true;
applied = true;
break 'headers;
}
if let Some(merge) = terminal_exit_bridge_merge(&out, &forest, &merges, &header) {
merges.insert(header, merge);
changed = true;
applied = true;
break 'headers;
}
if let Some(convergence) = terminal_exit_convergence(&out, &forest, &header) {
let mut candidate = out.clone();
let mut candidate_counter = counter;
let Some(merge) = synth_terminal_selection_merge(
&mut candidate,
&forest,
&header,
&convergence,
&mut candidate_counter,
) else {
continue;
};
out = candidate;
counter = candidate_counter;
merges.insert(header, merge);
changed = true;
applied = true;
break 'headers;
}
}
if !applied {
break;
}
}
changed.then_some(TerminalExitSelectionPlan {
blocks: out,
merges,
})
}
pub(in crate::native) fn synth_terminal_selection_merge(
blocks: &mut Vec<BodyBlock>,
forest: &LoopForest,
header: &str,
continuation: &str,
counter: &mut usize,
) -> Option<String> {
if block_has_phi(blocks, continuation) {
synth_unique_selection_merge_phi(blocks, forest, header, continuation, counter)
} else {
synth_unique_selection_merge(blocks, forest, header, continuation, counter)
}
}
pub(in crate::native) fn terminal_unreachable_selection_merges(
blocks: &[BodyBlock],
) -> Option<TerminalExitSelectionPlan> {
if blocks.len() > TERMINAL_EXIT_SELECTION_MAX_BLOCKS {
return None;
}
let mut out = blocks.to_vec();
let mut merges = HashMap::new();
let mut counter = TERMINAL_EXIT_COUNTER_START;
let mut changed = false;
for _ in 0..MAX_TERMINAL_EXIT_SELECTION_ROUNDS {
let forest = analyze(&out);
let loop_nodes: HashSet<&str> = forest
.loops
.iter()
.flat_map(|loop_info| loop_info.body.iter().map(String::as_str))
.collect();
let depth = |name: &str| {
let mut depth = 0usize;
let mut current = name;
while let Some(parent) = forest.idom(current) {
depth += 1;
current = parent;
}
depth
};
let mut headers: Vec<String> = out
.iter()
.filter(|block| !merges.contains_key(&block.name))
.filter(|block| !loop_nodes.contains(block.name.as_str()))
.filter(|block| conditional_branch_targets(block).is_some())
.map(|block| block.name.clone())
.collect();
headers.sort_by(|left, right| depth(right).cmp(&depth(left)).then(left.cmp(right)));
let mut applied = false;
for header in headers {
let Some((merge, exit_arm, return_arm)) =
terminal_unreachable_selection(&out, &forest, &header)
else {
continue;
};
let mut candidate = out.clone();
let mut candidate_counter = counter;
if synth_terminal_return_clone(
&mut candidate,
&header,
&exit_arm,
&return_arm,
&mut candidate_counter,
)
.is_none()
{
continue;
}
out = candidate;
counter = candidate_counter;
merges.insert(header, merge);
changed = true;
applied = true;
break;
}
if !applied {
break;
}
}
changed.then_some(TerminalExitSelectionPlan {
blocks: out,
merges,
})
}
pub(in crate::native) struct TerminalExitContinuation {
pub(in crate::native) continuation: String,
pub(in crate::native) exit_arm: String,
pub(in crate::native) return_arm: TerminalReturnArm,
}
pub(in crate::native) struct TerminalReturnArm {
pub(in crate::native) return_block: String,
pub(in crate::native) predecessor: Option<String>,
}
pub(in crate::native) fn terminal_exit_continuation(
blocks: &[BodyBlock],
forest: &LoopForest,
header: &str,
) -> Option<TerminalExitContinuation> {
let block = blocks.iter().find(|block| block.name == header)?;
let (left, right) = conditional_branch_targets(block)?;
let left_exit = terminal_exit_arm(blocks, forest, header, &left);
let right_exit = terminal_exit_arm(blocks, forest, header, &right);
match (left_exit, right_exit) {
(None, Some(return_arm)) => Some(TerminalExitContinuation {
continuation: left,
exit_arm: right,
return_arm,
}),
(Some(return_arm), None) => Some(TerminalExitContinuation {
continuation: right,
exit_arm: left,
return_arm,
}),
_ => None,
}
}
pub(in crate::native) fn terminal_unreachable_selection(
blocks: &[BodyBlock],
forest: &LoopForest,
header: &str,
) -> Option<(String, String, TerminalReturnArm)> {
let block = blocks.iter().find(|block| block.name == header)?;
let (left, right) = conditional_branch_targets(block)?;
let left_return = terminal_exit_arm(blocks, forest, header, &left);
let right_return = terminal_exit_arm(blocks, forest, header, &right);
let left_unreachable = block_ends_in_unreachable(blocks, &left);
let right_unreachable = block_ends_in_unreachable(blocks, &right);
let (merge, exit_arm, return_arm) = match (
left_return,
right_return,
left_unreachable,
right_unreachable,
) {
(Some(return_arm), None, false, true) => (right, left, return_arm),
(None, Some(return_arm), true, false) => (left, right, return_arm),
_ => return None,
};
forest
.dominates(header, &merge)
.then_some((merge, exit_arm, return_arm))
}
pub(in crate::native) fn block_ends_in_unreachable(blocks: &[BodyBlock], name: &str) -> bool {
blocks
.iter()
.find(|block| block.name == name)
.is_some_and(is_bare_unreachable)
}
fn is_bare_ret_void(block: &BodyBlock) -> bool {
block
.typed
.as_ref()
.is_some_and(|t| t.insts.is_empty() && matches!(t.ret, crate::native::tir::RetEmit::Void))
}
fn is_bare_unreachable(block: &BodyBlock) -> bool {
block.typed.as_ref().is_some_and(|t| {
t.insts.is_empty() && matches!(t.terminator, crate::native::tir::TirTerminator::Unreachable)
})
}
pub(in crate::native) fn terminal_exit_arm(
blocks: &[BodyBlock],
forest: &LoopForest,
header: &str,
arm: &str,
) -> Option<TerminalReturnArm> {
if block_ends_in_void_return(blocks, arm) {
return Some(TerminalReturnArm {
return_block: arm.to_string(),
predecessor: None,
});
}
if !forest.dominates(header, arm) {
return None;
}
let mut current = arm.to_string();
let mut seen = HashSet::new();
while seen.insert(current.clone()) {
let block = blocks.iter().find(|block| block.name == current)?;
if block_ends_in_void_return(blocks, ¤t) {
return Some(TerminalReturnArm {
return_block: current,
predecessor: None,
});
}
let successors = block_successors(block);
if successors.len() != 1 {
return None;
}
let next = successors.into_iter().next()?;
if block_ends_in_void_return(blocks, &next) {
return Some(TerminalReturnArm {
return_block: next,
predecessor: Some(current),
});
}
if !forest.dominates(header, &next) {
return None;
}
current = next;
}
None
}
pub(in crate::native) fn synth_terminal_return_clone(
blocks: &mut Vec<BodyBlock>,
header: &str,
exit_arm: &str,
return_arm: &TerminalReturnArm,
counter: &mut usize,
) -> Option<String> {
if !block_ends_in_void_return(blocks, &return_arm.return_block) {
return None;
}
let (redirect_block, redirect_from) = match &return_arm.predecessor {
Some(predecessor) => (predecessor.as_str(), return_arm.return_block.as_str()),
None => (header, exit_arm),
};
let redirect_index = blocks
.iter()
.position(|block| block.name == redirect_block)?;
if !block_successors(blocks.get(redirect_index)?)
.iter()
.any(|s| s == redirect_from)
{
return None;
}
let names: HashSet<&str> = blocks.iter().map(|block| block.name.as_str()).collect();
let mut id = *counter;
let return_name = loop {
let candidate = format!("{SPLIT_PREFIX}{TEXITRET_TOKEN}{id}");
if !names.contains(candidate.as_str()) {
break candidate;
}
id += 1;
};
*counter = id + 1;
blocks
.get_mut(redirect_index)?
.typed
.as_mut()?
.redirect_successor(redirect_from, &return_name);
blocks.push(synthetic_block(
return_name.clone(),
vec!["ret void".to_string()],
BlockRole::TerminalExitReturn,
));
Some(return_name)
}
pub(in crate::native) fn terminal_exit_bridge_merge(
blocks: &[BodyBlock],
forest: &LoopForest,
terminal_merges: &HashMap<String, String>,
header: &str,
) -> Option<String> {
let block = blocks.iter().find(|block| block.name == header)?;
let (left, right) = conditional_branch_targets(block)?;
for (continuation, terminal_arm) in [(&left, &right), (&right, &left)] {
let Some(terminal_merge) = terminal_merges.get(terminal_arm) else {
continue;
};
let Some(merge_block) = blocks.iter().find(|block| block.name == *terminal_merge) else {
continue;
};
let successors = block_successors(merge_block);
if forest.dominates(header, continuation)
&& successors.len() == 1
&& successors.first() == Some(continuation)
{
return Some(continuation.clone());
}
}
None
}
pub(in crate::native) fn terminal_exit_convergence(
blocks: &[BodyBlock],
forest: &LoopForest,
header: &str,
) -> Option<String> {
let header_block = blocks.iter().find(|block| block.name == header)?;
let arms = conditional_branch_targets(header_block)?;
let loop_nodes: HashSet<&str> = forest
.loops
.iter()
.flat_map(|loop_info| loop_info.body.iter().map(String::as_str))
.collect();
let private_return = |name: &str| {
blocks
.iter()
.find(|block| block.name == name)
.is_some_and(|block| block.role == BlockRole::TerminalExitReturn)
&& block_ends_in_void_return(blocks, name)
};
let depth = |name: &str| {
let mut depth = 0usize;
let mut current = name;
while let Some(parent) = forest.idom(current) {
depth += 1;
current = parent;
}
depth
};
let mut candidates: Vec<&str> = blocks
.iter()
.filter(|block| {
block.name != header
&& !matches!(block.role, BlockRole::LMerge | BlockRole::TerminalExitReturn)
&& !loop_nodes.contains(block.name.as_str())
})
.map(|block| block.name.as_str())
.collect();
candidates.sort_by_key(|candidate| (depth(candidate), *candidate));
for candidate in candidates {
let mut terminal_paths = 0usize;
let mut valid = true;
for arm in [&arms.0, &arms.1] {
let mut reached = false;
let mut saw_terminal = false;
let mut seen = HashSet::new();
let mut stack = vec![arm.clone()];
while let Some(node) = stack.pop() {
if node == candidate {
reached = true;
continue;
}
if private_return(&node) {
saw_terminal = true;
continue;
}
if !forest.dominates(header, &node) || loop_nodes.contains(node.as_str()) {
valid = false;
break;
}
if !seen.insert(node.clone()) {
continue;
}
let Some(block) = blocks.iter().find(|block| block.name == node) else {
valid = false;
break;
};
let successors = block_successors(block);
if successors.is_empty() {
valid = false;
break;
}
stack.extend(successors);
}
if !valid || !reached {
valid = false;
break;
}
terminal_paths += saw_terminal as usize;
}
if valid && terminal_paths > 0 {
return Some(candidate.to_string());
}
}
None
}