use super::*;
impl<'a, 'b> StructuredBodyLowerer<'a, 'b> {
pub(super) fn lower_branch(
&mut self,
block: BlockRef,
stop: Option<BlockRef>,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
if let Some(next) =
self.try_lower_conditional_reassign_branch(block, stop, stmts, target_overrides)
{
return Some(next);
}
if let Some(next) =
self.try_lower_statement_value_merge_branch(block, stop, stmts, target_overrides)
{
return Some(next);
}
if let Some(next) = self.try_lower_value_merge_branch(block, stop, stmts, target_overrides)
{
return Some(next);
}
if let Some(next) =
self.try_lower_loop_continue_branch(block, stop, stmts, target_overrides)
{
return Some(next);
}
if let Some(next) = self.try_lower_loop_break_branch(block, stop, stmts, target_overrides) {
return Some(next);
}
if let Some(escape_target) = self.cross_structure_escape_target(block) {
return self.lower_cross_structure_escape_branch(
block,
escape_target,
stop,
stmts,
target_overrides,
);
}
stmts.extend(self.lower_block_prefix(block, true, target_overrides)?);
let short_plan = self.try_build_short_circuit_plan(block, stop)?;
let plan = short_plan.or_else(|| self.build_plain_branch_plan(block))?;
for header in &plan.consumed_headers {
self.visited.insert(*header);
}
let branch_stop =
self.branch_stop_for_region(block, plan.then_entry, plan.else_entry, plan.merge, stop);
let branch_target_overrides = self
.branch_value_merges_by_header
.contains_key(&block)
.then(|| {
self.branch_value_target_overrides(block, target_overrides)
});
if let Some(branch_target_overrides) = branch_target_overrides.as_ref() {
stmts.extend(self.branch_value_preserved_entry_stmts(block, branch_target_overrides));
}
let then_target_overrides = branch_target_overrides
.as_ref()
.map(|branch_target_overrides| {
self.branch_value_then_target_overrides(block, branch_target_overrides)
})
.unwrap_or_else(|| target_overrides.clone());
let else_target_overrides = branch_target_overrides
.as_ref()
.map(|branch_target_overrides| {
self.branch_value_else_target_overrides(block, branch_target_overrides)
})
.unwrap_or_else(|| target_overrides.clone());
let then_block = self.lower_region(plan.then_entry, branch_stop, &then_target_overrides)?;
let else_block = match plan.else_entry {
Some(else_entry) => {
Some(self.lower_region(else_entry, branch_stop, &else_target_overrides)?)
}
None => None,
};
stmts.push(branch_stmt({
let mut cond = plan.cond;
rewrite_expr_temps(&mut cond, &temp_expr_overrides(target_overrides));
cond
}, then_block, else_block));
self.install_stop_boundary_value_merge_override(block, branch_stop, target_overrides);
for header in &plan.consumed_headers {
let branch_value_overrides = if *header == block {
branch_target_overrides
.clone()
.unwrap_or_else(|| target_overrides.clone())
} else {
self.branch_value_target_overrides(*header, target_overrides)
};
self.install_branch_value_merge_overrides(*header, &branch_value_overrides);
}
match branch_stop {
Some(next) if next == self.lowering.cfg.exit_block => Some(None),
Some(next) => Some(Some(next)),
None => Some(None),
}
}
fn try_lower_conditional_reassign_branch(
&mut self,
block: BlockRef,
stop: Option<BlockRef>,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
let short = value_merge_candidate_by_header(self.lowering, block)?;
let ShortCircuitExit::ValueMerge(merge) = short.exit else {
return None;
};
if Some(merge) == stop {
return None;
}
let plan = build_conditional_reassign_plan(self.lowering, block)?;
if let Some(stop) = stop
&& stop != merge
&& short.blocks.contains(&stop)
{
return None;
}
stmts.extend(self.lower_block_prefix(block, true, target_overrides)?);
self.visited.insert(block);
self.visited.extend(value_merge_skipped_blocks(short));
self.overrides.suppress_phi(plan.phi_id);
stmts.push(assign_stmt(
vec![HirLValue::Temp(plan.target_temp)],
vec![plan.init_value],
));
stmts.push(branch_stmt(
plan.cond,
HirBlock {
stmts: vec![assign_stmt(
vec![HirLValue::Temp(plan.target_temp)],
vec![plan.assigned_value],
)],
},
None,
));
Some(Some(plan.merge))
}
fn try_lower_statement_value_merge_branch(
&mut self,
block: BlockRef,
stop: Option<BlockRef>,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
let short = value_merge_candidate_by_header(self.lowering, block)?;
let ShortCircuitExit::ValueMerge(merge) = short.exit else {
return None;
};
if Some(merge) == stop {
return None;
}
let allowed_blocks = BTreeSet::from([block]);
if recover_short_value_merge_expr_with_allowed_blocks(self.lowering, short, &allowed_blocks)
.is_some()
{
return None;
}
if let Some(stop) = stop
&& stop != merge
&& short.blocks.contains(&stop)
{
return None;
}
let target_temp = *self
.lowering
.bindings
.phi_temps
.get(short.result_phi_id?.index())?;
let mut short_stmts = self.lower_block_prefix(block, true, target_overrides)?;
short_stmts.extend(
self.lower_value_merge_node(short, short.entry, target_temp, true)?
.stmts,
);
self.visited.insert(block);
self.visited.extend(value_merge_skipped_blocks(short));
self.overrides.suppress_phi(short.result_phi_id?);
stmts.extend(short_stmts);
Some(Some(merge))
}
fn try_lower_value_merge_branch(
&mut self,
block: BlockRef,
stop: Option<BlockRef>,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
let short = value_merge_candidate_by_header(self.lowering, block)?;
let ShortCircuitExit::ValueMerge(merge) = short.exit else {
return None;
};
if Some(merge) == stop {
return None;
}
let allowed_blocks = BTreeSet::from([block]);
let recovery = recover_short_value_merge_expr_recovery_with_allowed_blocks(
self.lowering,
short,
&allowed_blocks,
)?;
if let Some(stop) = stop
&& stop != merge
&& short.blocks.contains(&stop)
{
return None;
}
if recovery.consumes_header_subject() {
self.overrides
.suppress_instrs(consumed_value_merge_subject_instrs(self.lowering, block));
}
stmts.extend(self.lower_block_prefix(block, true, target_overrides)?);
self.visited.insert(block);
self.visited.extend(value_merge_skipped_blocks(short));
self.merge_allowed_blocks
.entry(merge)
.or_default()
.insert(block);
Some(Some(merge))
}
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
})?;
let break_block = if break_exit == loop_context.post_loop
|| Some(break_exit) == loop_context.downstream_post_loop
{
HirBlock {
stmts: vec![HirStmt::Break],
}
} else {
loop_context.break_exits[&break_exit].clone()
};
let body_stop = loop_context
.continue_target
.filter(|target| {
*target != break_exit && self.lowering.cfg.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),
}
}
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 candidate.else_entry.is_some()
|| merge == loop_context.post_loop
|| Some(merge) == loop_context.downstream_post_loop
|| loop_context.break_exits.contains_key(&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
.lowering
.cfg
.can_reach(candidate.then_entry, continue_target))
.then_some(merge)
}
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))
}
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 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 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,
);
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.insert(break_exit);
stmts.push(branch_stmt(
continue_cond.negate(),
loop_context.break_exits[&break_exit].clone(),
None,
));
return Some(None);
}
let continue_block = HirBlock {
stmts: vec![HirStmt::Continue],
};
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.insert(non_continue_entry);
if prefer_natural_fallthrough {
stmts.push(branch_stmt(
continue_cond.negate(),
break_block.clone(),
None,
));
return Some(None);
}
let stmt = if candidate.then_entry == continue_target {
branch_stmt(continue_cond, continue_block, Some(break_block.clone()))
} else {
branch_stmt(
continue_cond.negate(),
break_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 Some(Some(continue_target));
}
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 non_continue_block =
self.lower_region(non_continue_entry, 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)?;
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)?;
stmts.push(branch_stmt(continue_cond, continue_block, None));
if merge == self.lowering.cfg.exit_block {
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)
&& matches!(
self.block_terminator(non_continue_entry),
Some((_instr_ref, LowInstr::Return(_) | LowInstr::TailCall(_)))
)
&& !self
.lowering
.cfg
.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
}
fn lower_value_merge_node(
&self,
short: &ShortCircuitCandidate,
node_ref: ShortCircuitNodeRef,
target_temp: TempId,
prefix_emitted: bool,
) -> Option<HirBlock> {
let node = short.nodes.get(node_ref.index())?;
let mut stmts = Vec::new();
if !prefix_emitted {
stmts.extend(self.lower_block_prefix(node.header, true, &BTreeMap::new())?);
}
let cond = lower_short_circuit_subject(self.lowering, node.header)?;
let truthy =
self.lower_value_merge_target(short, node.header, &node.truthy, target_temp)?;
let falsy = self.lower_value_merge_target(short, node.header, &node.falsy, target_temp)?;
stmts.push(branch_stmt(cond, truthy, Some(falsy)));
Some(HirBlock { stmts })
}
fn branch_entry_target_overrides(
&self,
header: BlockRef,
entry: Option<BlockRef>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> BTreeMap<TempId, HirLValue> {
let Some(entry) = entry else {
return target_overrides.clone();
};
let Some(candidate) = self.branch_by_header.get(&header).copied() else {
return target_overrides.clone();
};
if entry == candidate.then_entry {
return self.branch_value_then_target_overrides(header, target_overrides);
}
if Some(entry) == candidate.else_entry {
return self.branch_value_else_target_overrides(header, target_overrides);
}
target_overrides.clone()
}
fn lower_value_merge_target(
&self,
short: &ShortCircuitCandidate,
current_header: BlockRef,
target: &ShortCircuitTarget,
target_temp: TempId,
) -> Option<HirBlock> {
match target {
ShortCircuitTarget::Node(next_ref) => {
self.lower_value_merge_node(short, *next_ref, target_temp, false)
}
ShortCircuitTarget::Value(block) => {
self.lower_value_merge_leaf(short, current_header, *block, target_temp)
}
ShortCircuitTarget::TruthyExit | ShortCircuitTarget::FalsyExit => None,
}
}
fn lower_value_merge_leaf(
&self,
short: &ShortCircuitCandidate,
current_header: BlockRef,
block: BlockRef,
target_temp: TempId,
) -> Option<HirBlock> {
let mut stmts = if block == current_header {
Vec::new()
} else {
self.lower_block_prefix(block, false, &BTreeMap::new())?
};
let value = if block == current_header {
lower_short_circuit_subject(self.lowering, block)?
} else {
lower_materialized_value_leaf_expr(self.lowering, short, block)?
};
stmts.push(assign_stmt(vec![HirLValue::Temp(target_temp)], vec![value]));
Some(HirBlock { stmts })
}
fn install_stop_boundary_value_merge_override(
&mut self,
header: BlockRef,
branch_stop: Option<BlockRef>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) {
let Some(merge) = branch_stop else {
return;
};
let Some(short) = value_merge_candidate_by_header(self.lowering, header) else {
return;
};
let ShortCircuitExit::ValueMerge(short_merge) = short.exit else {
return;
};
if short_merge != merge {
return;
}
let Some(phi_id) = short.result_phi_id else {
return;
};
let Some(reg) = short.result_reg else {
return;
};
let Some(expr) = shared_target_expr_from_overrides(self.lowering, short, target_overrides)
else {
return;
};
self.replace_phi_with_entry_expr(merge, phi_id, reg, expr);
}
}