use super::*;
impl StructuredBodyLowerer<'_, '_> {
pub(super) fn try_lower_loop_break_branch(
&mut self,
block: BlockRef,
stop: Option<BlockRef>,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
let loop_context = self.active_loops.last()?.clone();
let candidate = *self.branch_by_header.get(&block)?;
let break_exit = candidate.merge.filter(|merge| {
loop_context.break_exits.contains_key(merge)
|| *merge == loop_context.post_loop
|| Some(*merge) == loop_context.downstream_post_loop
})?;
if let Some(stop) = stop
&& stop != break_exit
&& loop_context.continue_target != Some(stop)
&& self
.branch_regions_by_header
.get(&block)
.is_some_and(|region| region.structured_blocks.contains(&stop))
{
return None;
}
if self.block_exits_outer_active_loop(break_exit) {
return None;
}
let pad_stmts = match candidate.else_entry {
Some(else_entry)
if else_entry != break_exit
&& Some(else_entry) != loop_context.downstream_post_loop =>
{
let is_direct_jump = self.block_terminator(else_entry).is_some_and(|(_, instr)| {
if let LowInstr::Jump(jump) = instr {
let target = self.lowering.cfg.instr_to_block[jump.target.index()];
target == break_exit || Some(target) == loop_context.downstream_post_loop
} else {
false
}
});
if !is_direct_jump {
return None;
}
let pad_stmts = self.lower_block_prefix(else_entry, false, target_overrides)?;
self.visited.insert(else_entry);
pad_stmts
}
_ => Vec::new(),
};
let break_block = if break_exit == loop_context.post_loop
|| Some(break_exit) == loop_context.downstream_post_loop
{
let mut stmts = pad_stmts;
stmts.push(HirStmt::Break);
HirBlock { stmts }
} else {
loop_context.break_exits[&break_exit].block.clone()
};
let body_stop = loop_context
.continue_target
.filter(|target| {
*target != break_exit
&& (candidate.then_entry == *target
|| self.can_reach(candidate.then_entry, *target))
})
.or(Some(break_exit));
let then_block = self.lower_region(candidate.then_entry, body_stop, target_overrides)?;
let mut cond = self.lower_candidate_cond(block, candidate)?;
rewrite_expr_temps(&mut cond, &temp_expr_overrides(target_overrides));
stmts.extend(self.lower_block_prefix(block, true, target_overrides)?);
self.visited.insert(block);
if break_exit != loop_context.post_loop
&& Some(break_exit) != loop_context.downstream_post_loop
{
self.visited.insert(break_exit);
}
if body_stop == Some(break_exit)
&& break_block.stmts.last() == Some(&HirStmt::Break)
&& then_block.stmts == break_block.stmts[..break_block.stmts.len() - 1]
{
stmts.extend(then_block.stmts);
stmts.push(branch_stmt(
cond.negate(),
HirBlock {
stmts: vec![HirStmt::Break],
},
None,
));
return Some(None);
}
if then_block.stmts.is_empty() {
stmts.push(branch_stmt(cond.negate(), break_block, None));
} else {
stmts.push(branch_stmt(cond, then_block, Some(break_block)));
}
match body_stop {
Some(next) if next == break_exit => Some(None),
Some(next) if next == self.lowering.cfg.exit_block => Some(None),
Some(next) => Some(Some(next)),
None => Some(None),
}
}
pub(super) fn try_lower_loop_terminal_else_branch(
&mut self,
block: BlockRef,
stop: Option<BlockRef>,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
let loop_context = self.active_loops.last()?.clone();
let stop = stop?;
if loop_context.continue_target != Some(stop) {
return None;
}
let candidate = *self.branch_by_header.get(&block)?;
let merge = candidate.merge?;
if candidate.else_entry.is_some()
|| merge == stop
|| self.branch_value_merges_by_header.contains_key(&block)
|| !self.can_reach_avoiding_block(candidate.then_entry, stop, merge)
|| !self.branch_arm_terminates_before_stop(merge, stop)
{
return None;
}
let then_target_overrides =
self.branch_entry_target_overrides(block, Some(candidate.then_entry), target_overrides);
let then_block =
self.lower_region(candidate.then_entry, Some(stop), &then_target_overrides)?;
let else_block = self.lower_region(merge, Some(stop), target_overrides)?;
let mut cond = self.lower_candidate_cond(block, candidate)?;
rewrite_expr_temps(&mut cond, &temp_expr_overrides(target_overrides));
stmts.extend(self.lower_block_prefix(block, true, target_overrides)?);
self.visited.insert(block);
stmts.push(branch_stmt(cond, then_block, Some(else_block)));
Some(Some(stop))
}
pub(super) fn cross_structure_escape_target(&self, block: BlockRef) -> Option<BlockRef> {
let loop_context = self.active_loops.last()?;
let candidate = self.branch_by_header.get(&block).copied()?;
let merge = candidate.merge?;
let continue_target = loop_context.continue_target?;
if self.block_exits_outer_active_loop(merge)
&& (candidate.then_entry == continue_target
|| self.can_reach(candidate.then_entry, continue_target))
{
return Some(merge);
}
if candidate.else_entry.is_some()
|| merge == loop_context.post_loop
|| Some(merge) == loop_context.downstream_post_loop
|| loop_context.break_exits.contains_key(&merge)
|| self.block_is_terminal_exit(merge)
{
return None;
}
let loop_candidate = self.loop_by_header.get(&loop_context.header).copied()?;
if loop_candidate.blocks.contains(&merge) {
return None;
}
(candidate.then_entry == continue_target
|| self.can_reach(candidate.then_entry, continue_target))
.then_some(merge)
}
pub(super) fn lower_cross_structure_escape_branch(
&mut self,
block: BlockRef,
escape_target: BlockRef,
_stop: Option<BlockRef>,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
let loop_context = self.active_loops.last()?.clone();
let continue_target = loop_context.continue_target?;
let candidate = *self.branch_by_header.get(&block)?;
let mut keep_cond = self.lower_candidate_cond(block, candidate)?;
rewrite_expr_temps(&mut keep_cond, &temp_expr_overrides(target_overrides));
stmts.extend(self.lower_block_prefix(block, true, target_overrides)?);
self.visited.insert(block);
let escape_block = self.lower_escape_edge(block, escape_target, target_overrides)?;
let continue_block = if candidate.then_entry == continue_target {
HirBlock::default()
} else {
self.lower_region(
candidate.then_entry,
Some(continue_target),
target_overrides,
)?
};
let continue_else = (!continue_block.stmts.is_empty()).then_some(continue_block);
stmts.push(branch_stmt(keep_cond.negate(), escape_block, continue_else));
Some(Some(continue_target))
}
pub(super) fn try_lower_loop_continue_branch(
&mut self,
block: BlockRef,
stop: Option<BlockRef>,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
let loop_context = self.active_loops.last()?.clone();
let continue_target = loop_context.continue_target?;
let continue_target_is_empty = self.loop_continue_target_is_empty(continue_target);
let can_fallthrough_to_non_empty_continue = self
.loop_by_header
.get(&loop_context.header)
.is_some_and(|candidate| {
matches!(
candidate.kind_hint,
LoopKindHint::NumericForLike
| LoopKindHint::GenericForLike
| LoopKindHint::Unknown
)
});
if !continue_target_is_empty && !can_fallthrough_to_non_empty_continue {
return None;
}
if let Some(short_plan) = self.try_build_short_circuit_plan(block, stop).flatten() {
let short_plan_has_continue_edge = short_plan.then_entry == continue_target
|| short_plan.else_entry == Some(continue_target);
if !short_plan_has_continue_edge {
return None;
}
}
let branch_points_to_continue =
self.branch_by_header.get(&block).is_some_and(|candidate| {
candidate.then_entry == continue_target
|| candidate.else_entry == Some(continue_target)
|| candidate.merge == Some(continue_target)
});
if !loop_context.continue_sources.contains(&block) && !branch_points_to_continue {
return None;
}
let candidate = *self.branch_by_header.get(&block)?;
if candidate.then_entry != continue_target
&& candidate.else_entry != Some(continue_target)
&& candidate.merge != Some(continue_target)
{
return None;
}
if candidate.merge == Some(continue_target)
&& candidate.else_entry.is_some()
&& candidate.then_entry != continue_target
&& candidate.else_entry != Some(continue_target)
{
return None;
}
if self
.non_continue_entry_for_continue_candidate(candidate, continue_target)
.is_some_and(|entry| self.entry_is_direct_loop_break(entry, &loop_context))
{
return None;
}
let mut continue_cond = self.lower_branch_cond_for_target(block, continue_target)?;
rewrite_expr_temps(&mut continue_cond, &temp_expr_overrides(target_overrides));
let prefer_natural_fallthrough = self.prefer_natural_fallthrough_over_continue(
block,
candidate,
continue_target,
&loop_context,
);
if !continue_target_is_empty
&& !prefer_natural_fallthrough
&& candidate.merge != Some(continue_target)
{
return None;
}
let then_target_overrides =
self.branch_entry_target_overrides(block, Some(candidate.then_entry), target_overrides);
stmts.extend(self.lower_block_prefix(block, true, target_overrides)?);
self.visited.insert(block);
if let Some(break_exit) = candidate
.merge
.filter(|merge| loop_context.break_exits.contains_key(merge))
{
self.visited
.extend(loop_context.break_exits[&break_exit].blocks.iter().copied());
stmts.push(branch_stmt(
continue_cond.negate(),
loop_context.break_exits[&break_exit].block.clone(),
None,
));
return Some(None);
}
if let Some(else_entry) = candidate.else_entry {
let non_continue_entry = if candidate.then_entry == continue_target {
else_entry
} else {
candidate.then_entry
};
if let Some(break_block) = loop_context.break_exits.get(&non_continue_entry) {
self.visited.extend(break_block.blocks.iter().copied());
if prefer_natural_fallthrough {
stmts.push(branch_stmt(
continue_cond.negate(),
break_block.block.clone(),
None,
));
return Some(None);
}
let continue_block = self.explicit_continue_block()?;
let stmt = if candidate.then_entry == continue_target {
branch_stmt(
continue_cond,
continue_block,
Some(break_block.block.clone()),
)
} else {
branch_stmt(
continue_cond.negate(),
break_block.block.clone(),
Some(continue_block),
)
};
stmts.push(stmt);
return Some(None);
}
if prefer_natural_fallthrough {
let non_continue_target_overrides = self.branch_entry_target_overrides(
block,
Some(non_continue_entry),
target_overrides,
);
let non_continue_block = self.lower_region(
non_continue_entry,
Some(continue_target),
&non_continue_target_overrides,
)?;
stmts.push(branch_stmt(
continue_cond.negate(),
non_continue_block,
None,
));
return if !continue_target_is_empty && continue_target == loop_context.header {
Some(None)
} else {
Some(Some(continue_target))
};
}
let continue_block = self.explicit_continue_block()?;
let branch_stop = self.branch_stop_for_region(
block,
candidate.then_entry,
candidate.else_entry,
candidate.merge,
stop,
);
let non_continue_target_overrides = self.branch_entry_target_overrides(
block,
Some(non_continue_entry),
target_overrides,
);
let non_continue_block = self.lower_region(
non_continue_entry,
branch_stop,
&non_continue_target_overrides,
)?;
let stmt = if candidate.then_entry == continue_target {
branch_stmt(continue_cond, continue_block, Some(non_continue_block))
} else {
branch_stmt(
continue_cond.negate(),
non_continue_block,
Some(continue_block),
)
};
stmts.push(stmt);
return match branch_stop {
Some(next) if next == self.lowering.cfg.exit_block => Some(None),
Some(next) => Some(Some(next)),
None => Some(None),
};
}
if candidate.then_entry == continue_target {
let non_continue_entry = candidate.merge?;
if self.prefer_natural_fallthrough_over_continue(
block,
candidate,
continue_target,
&loop_context,
) {
let branch_stop = if continue_target_is_empty {
stop
} else {
Some(continue_target)
};
let non_continue_block =
self.lower_region(non_continue_entry, branch_stop, target_overrides)?;
stmts.push(branch_stmt(
continue_cond.negate(),
non_continue_block,
None,
));
return Some(None);
}
let non_continue_block =
self.lower_region(non_continue_entry, stop, target_overrides)?;
let continue_block = self.explicit_continue_block()?;
stmts.push(branch_stmt(
continue_cond,
continue_block,
Some(non_continue_block),
));
return Some(None);
}
if candidate.merge == Some(continue_target) {
let non_continue_block = self.lower_region(
candidate.then_entry,
Some(continue_target),
&then_target_overrides,
)?;
stmts.push(branch_stmt(
continue_cond.negate(),
non_continue_block,
None,
));
return Some(Some(continue_target));
}
let merge = candidate.merge.or(stop)?;
let continue_block = self.explicit_continue_block()?;
stmts.push(branch_stmt(continue_cond, continue_block, None));
if merge == self.lowering.cfg.exit_block
|| (!continue_target_is_empty && continue_target == loop_context.header)
{
Some(None)
} else {
Some(Some(merge))
}
}
fn prefer_natural_fallthrough_over_continue(
&self,
block: BlockRef,
candidate: &BranchCandidate,
continue_target: BlockRef,
loop_context: &ActiveLoopContext,
) -> bool {
if candidate.merge == Some(continue_target) {
return false;
}
let Some(non_continue_entry) =
self.non_continue_entry_for_continue_candidate(candidate, continue_target)
else {
return false;
};
if !loop_context.continue_sources.contains(&block) {
return true;
}
if matches!(
self.block_terminator(non_continue_entry),
Some((_instr_ref, LowInstr::Return(_) | LowInstr::TailCall(_)))
) && !self.can_reach(non_continue_entry, continue_target)
{
return true;
}
self.entry_is_break_funnel_to_continue(
non_continue_entry,
continue_target,
loop_context,
&mut BTreeSet::new(),
)
}
fn non_continue_entry_for_continue_candidate(
&self,
candidate: &BranchCandidate,
continue_target: BlockRef,
) -> Option<BlockRef> {
if candidate.then_entry == continue_target {
candidate.else_entry.or(candidate.merge)
} else if candidate.else_entry == Some(continue_target) {
Some(candidate.then_entry)
} else {
None
}
}
fn entry_is_break_funnel_to_continue(
&self,
entry: BlockRef,
continue_target: BlockRef,
loop_context: &ActiveLoopContext,
visited: &mut BTreeSet<BlockRef>,
) -> bool {
if !visited.insert(entry) {
return false;
}
if self.entry_is_direct_loop_break(entry, loop_context) {
return true;
}
let Some(candidate) = self.branch_by_header.get(&entry).copied() else {
return false;
};
let Some(non_continue_entry) =
self.non_continue_entry_for_continue_candidate(candidate, continue_target)
else {
return false;
};
self.entry_is_break_funnel_to_continue(
non_continue_entry,
continue_target,
loop_context,
visited,
)
}
fn entry_is_direct_loop_break(
&self,
entry: BlockRef,
loop_context: &ActiveLoopContext,
) -> bool {
loop_context.break_exits.contains_key(&entry)
|| entry == loop_context.post_loop
|| Some(entry) == loop_context.downstream_post_loop
}
}