use super::*;
use crate::cfg::DefId;
#[derive(Debug, Clone, Copy)]
struct SharedContinuationBranch {
gated_entry: BlockRef,
shared_entry: BlockRef,
negate_cond: bool,
}
type StatementValueMergeOutput<'c> = (&'c ShortCircuitCandidate, TempId);
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>> {
let checkpoint = self.checkpoint_state(stmts.len());
if let Some(next) =
self.try_lower_single_pass_repeat_branch(block, stop, stmts, target_overrides)
{
return Some(next);
}
self.restore_state_checkpoint(checkpoint, stmts);
let checkpoint = self.checkpoint_state(stmts.len());
if let Some(next) =
self.try_lower_conditional_reassign_branch(block, stop, stmts, target_overrides)
{
return Some(next);
}
self.restore_state_checkpoint(checkpoint, stmts);
let checkpoint = self.checkpoint_state(stmts.len());
if let Some(next) =
self.try_lower_statement_value_merge_branch(block, stop, stmts, target_overrides)
{
return Some(next);
}
self.restore_state_checkpoint(checkpoint, stmts);
let checkpoint = self.checkpoint_state(stmts.len());
if let Some(next) = self.try_lower_value_merge_branch(block, stop, stmts, target_overrides)
{
return Some(next);
}
self.restore_state_checkpoint(checkpoint, stmts);
let checkpoint = self.checkpoint_state(stmts.len());
if let Some(next) =
self.try_lower_branch_exit_value_assignment(block, stop, stmts, target_overrides)
{
return Some(next);
}
self.restore_state_checkpoint(checkpoint, stmts);
let checkpoint = self.checkpoint_state(stmts.len());
if let Some(next) =
self.try_lower_loop_continue_branch(block, stop, stmts, target_overrides)
{
return Some(next);
}
self.restore_state_checkpoint(checkpoint, stmts);
let checkpoint = self.checkpoint_state(stmts.len());
if let Some(next) = self.try_lower_loop_break_branch(block, stop, stmts, target_overrides) {
return Some(next);
}
self.restore_state_checkpoint(checkpoint, stmts);
let checkpoint = self.checkpoint_state(stmts.len());
if let Some(escape_target) = self.cross_structure_escape_target(block)
&& let Some(next) = self.lower_cross_structure_escape_branch(
block,
escape_target,
stop,
stmts,
target_overrides,
)
{
return Some(next);
}
self.restore_state_checkpoint(checkpoint, stmts);
let checkpoint = self.checkpoint_state(stmts.len());
if let Some(next) =
self.try_lower_terminal_else_guard_branch(block, stop, stmts, target_overrides)
{
return Some(next);
}
self.restore_state_checkpoint(checkpoint, stmts);
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))?;
if let Some(shared) = self.shared_continuation_branch(&plan, stop) {
let checkpoint = self.checkpoint_state(stmts.len());
if let Some(next) =
self.lower_shared_continuation_branch(shared, &plan, stmts, target_overrides)
{
return Some(next);
}
self.restore_state_checkpoint(checkpoint, stmts);
}
if let Some(shared) = self.terminal_loop_continuation_branch(&plan, stop) {
let checkpoint = self.checkpoint_state(stmts.len());
if let Some(next) =
self.lower_shared_continuation_branch(shared, &plan, stmts, target_overrides)
{
return Some(next);
}
self.restore_state_checkpoint(checkpoint, stmts);
}
for header in &plan.consumed_headers {
self.visited.insert(*header);
}
let mut branch_stop =
self.branch_stop_for_region(block, plan.then_entry, plan.else_entry, plan.merge, stop);
if let Some(downstream) = self.if_then_downstream_merge_stop(&plan, branch_stop, stop) {
branch_stop = Some(downstream);
}
let branch_value_headers = plan
.consumed_headers
.iter()
.copied()
.filter(|header| self.branch_value_merges_by_header.contains_key(header))
.collect::<Vec<_>>();
let branch_target_overrides = (!branch_value_headers.is_empty()).then(|| {
let mut overrides = target_overrides.clone();
for header in &branch_value_headers {
overrides = self.branch_value_target_overrides(*header, &overrides);
}
overrides
});
if let Some(branch_target_overrides) = branch_target_overrides.as_ref() {
for header in &branch_value_headers {
stmts.extend(
self.branch_value_preserved_entry_stmts(*header, 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 effective_else_entry = plan
.else_entry
.or_else(|| self.implicit_else_merge_entry(&plan, branch_stop));
let then_stop = if plan.else_entry.is_none()
&& effective_else_entry == plan.merge
&& branch_stop != plan.merge
&& Some(plan.then_entry) != branch_stop
&& plan.merge.is_some_and(|merge| {
!self.block_is_terminal_exit(merge)
&& !branch_stop.is_some_and(|stop| {
self.can_reach_avoiding_block(plan.then_entry, stop, merge)
})
}) {
plan.merge
} else {
self.branch_arm_stop(
plan.then_entry,
effective_else_entry,
plan.merge,
branch_stop,
)
};
let else_stop = effective_else_entry.and_then(|else_entry| {
self.branch_arm_stop(else_entry, Some(plan.then_entry), plan.merge, branch_stop)
});
let then_block = self.lower_region(plan.then_entry, then_stop, &then_target_overrides)?;
let else_block = match effective_else_entry {
Some(else_entry) => {
Some(self.lower_region(else_entry, else_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 = branch_target_overrides
.clone()
.unwrap_or_else(|| self.branch_value_target_overrides(*header, target_overrides));
self.install_branch_value_merge_overrides(*header, &branch_value_overrides);
}
if let Some(sc) = value_merge_candidate_by_header(self.lowering, block)
&& let ShortCircuitExit::ValueMerge(sc_merge) = sc.exit
&& branch_stop == Some(sc_merge)
{
self.merge_allowed_blocks
.entry(sc_merge)
.or_default()
.insert(block);
}
match branch_stop {
Some(next) if next == self.lowering.cfg.exit_block => Some(None),
Some(next) => Some(Some(next)),
None => Some(None),
}
}
fn if_then_downstream_merge_stop(
&self,
plan: &StructuredBranchPlan,
branch_stop: Option<BlockRef>,
region_stop: Option<BlockRef>,
) -> Option<BlockRef> {
let merge = plan.merge?;
if plan.else_entry.is_some()
|| branch_stop != Some(merge)
|| region_stop == Some(merge)
|| self.branch_by_header.contains_key(&merge)
|| self.loop_by_header.contains_key(&merge)
|| self.block_is_terminal_exit(merge)
{
return None;
}
let downstream = self.lowering.cfg.unique_reachable_successor(merge)?;
self.can_reach_avoiding_block(plan.then_entry, downstream, merge)
.then_some(downstream)
}
pub(super) fn can_reach_avoiding_block(
&self,
from: BlockRef,
to: BlockRef,
avoided: BlockRef,
) -> bool {
if from == avoided || to == avoided {
return false;
}
let mut allowed_blocks = self.lowering.cfg.reachable_blocks.clone();
allowed_blocks.remove(&avoided);
self.lowering
.cfg
.can_reach_within(from, to, &allowed_blocks)
}
fn implicit_else_merge_entry(
&self,
plan: &StructuredBranchPlan,
branch_stop: Option<BlockRef>,
) -> Option<BlockRef> {
let merge = plan.merge?;
if Some(merge) == branch_stop {
return None;
}
if self.block_is_terminal_exit(merge) {
return Some(merge);
}
let stop = branch_stop?;
if plan.else_entry.is_none()
&& plan.then_entry == stop
&& self.branch_arm_reaches_stop_or_loop_escape(merge, stop, stop)
{
return Some(merge);
}
self.lowering
.cfg
.unique_reachable_successor(merge)
.filter(|successor| *successor == stop)
.map(|_| merge)
}
fn shared_continuation_branch(
&self,
plan: &StructuredBranchPlan,
stop: Option<BlockRef>,
) -> Option<SharedContinuationBranch> {
if plan.consumed_headers.is_empty() {
return None;
}
let else_entry = plan.else_entry?;
let merge = plan.merge.unwrap_or(self.lowering.cfg.exit_block);
if self.active_loops.last().is_some_and(|loop_context| {
loop_context.continue_target.is_none()
&& loop_context.post_loop == merge
&& stop.is_some_and(|stop| {
self.branch_regions_by_header
.get(&plan.consumed_headers[0])
.is_some_and(|region| region.structured_blocks.contains(&stop))
})
}) {
return None;
}
if self.active_loops.last().is_some_and(|loop_context| {
loop_context.continue_target == Some(merge) && self.loop_continue_target_is_empty(merge)
}) {
return None;
}
if self.block_has_unstructured_continue_requirement(plan.then_entry)
|| self.block_has_unstructured_continue_requirement(else_entry)
{
return None;
}
if plan
.consumed_headers
.iter()
.any(|header| self.branch_value_merges_by_header.contains_key(header))
{
return None;
}
let merge_is_explicit = plan.merge.is_some();
let then_is_shared = else_entry != merge
&& plan.then_entry != merge
&& (merge_is_explicit
|| self.loop_preheader_exits_to_shared(else_entry, plan.then_entry))
&& self.entry_reaches_shared_continuation(else_entry, plan.then_entry, merge);
if then_is_shared {
return Some(SharedContinuationBranch {
gated_entry: else_entry,
shared_entry: plan.then_entry,
negate_cond: true,
});
}
let else_is_shared = else_entry != merge
&& plan.then_entry != merge
&& (merge_is_explicit
|| self.loop_preheader_exits_to_shared(plan.then_entry, else_entry))
&& self.entry_reaches_shared_continuation(plan.then_entry, else_entry, merge);
else_is_shared.then_some(SharedContinuationBranch {
gated_entry: plan.then_entry,
shared_entry: else_entry,
negate_cond: false,
})
}
fn terminal_loop_continuation_branch(
&self,
plan: &StructuredBranchPlan,
stop: Option<BlockRef>,
) -> Option<SharedContinuationBranch> {
if stop.is_some()
|| plan.merge.is_some()
|| plan.consumed_headers.len() != 1
|| plan
.consumed_headers
.iter()
.any(|header| self.branch_value_merges_by_header.contains_key(header))
{
return None;
}
let else_entry = plan.else_entry?;
if self.block_has_unstructured_continue_requirement(plan.then_entry)
|| self.block_has_unstructured_continue_requirement(else_entry)
{
return None;
}
if self.entry_is_terminal_generic_for_guard(else_entry, plan.then_entry) {
return Some(SharedContinuationBranch {
gated_entry: else_entry,
shared_entry: plan.then_entry,
negate_cond: true,
});
}
self.entry_is_terminal_generic_for_guard(plan.then_entry, else_entry)
.then_some(SharedContinuationBranch {
gated_entry: plan.then_entry,
shared_entry: else_entry,
negate_cond: false,
})
}
fn entry_is_terminal_generic_for_guard(&self, entry: BlockRef, shared: BlockRef) -> bool {
if self.lowering.cfg.can_reach(entry, shared) {
return false;
}
let Some(header) = self.lowering.cfg.unique_reachable_successor(entry) else {
return false;
};
let Some(candidate) = self.loop_by_header.get(&header).copied() else {
return false;
};
if !candidate.reducible
|| candidate.kind_hint != LoopKindHint::GenericForLike
|| candidate.preheader != Some(entry)
{
return false;
}
candidate.exits.iter().all(|exit| {
!self.lowering.cfg.can_reach(*exit, shared)
&& self.entry_must_reach_shared_or_terminate(
*exit,
shared,
self.lowering.cfg.exit_block,
)
})
}
fn loop_preheader_exits_to_shared(&self, preheader: BlockRef, shared: BlockRef) -> bool {
let Some(header) = self.lowering.cfg.unique_reachable_successor(preheader) else {
return false;
};
self.loop_by_header.get(&header).is_some_and(|candidate| {
candidate.preheader == Some(preheader) && candidate.exits.contains(&shared)
})
}
fn entry_reaches_shared_continuation(
&self,
entry: BlockRef,
shared: BlockRef,
boundary: BlockRef,
) -> bool {
self.entry_must_reach_or_escape_before_boundary(entry, shared, boundary)
|| self.entry_must_reach_shared_or_terminate(entry, shared, boundary)
}
fn entry_must_reach_shared_or_terminate(
&self,
entry: BlockRef,
shared: BlockRef,
boundary: BlockRef,
) -> bool {
fn visit(
lowerer: &StructuredBodyLowerer<'_, '_>,
block: BlockRef,
shared: BlockRef,
boundary: BlockRef,
visiting: &mut BTreeSet<BlockRef>,
memo: &mut BTreeMap<BlockRef, bool>,
) -> bool {
if block == shared {
return true;
}
if block == boundary || !lowerer.lowering.cfg.reachable_blocks.contains(&block) {
return false;
}
if block == lowerer.lowering.cfg.exit_block || lowerer.block_is_terminal_exit(block) {
return true;
}
if let Some(result) = memo.get(&block).copied() {
return result;
}
if !visiting.insert(block) {
return true;
}
let result = lowerer.lowering.cfg.succs[block.index()]
.iter()
.all(|edge_ref| {
let successor = lowerer.lowering.cfg.edges[edge_ref.index()].to;
visit(lowerer, successor, shared, boundary, visiting, memo)
});
visiting.remove(&block);
memo.insert(block, result);
result
}
visit(
self,
entry,
shared,
boundary,
&mut BTreeSet::new(),
&mut BTreeMap::new(),
)
}
fn try_lower_single_pass_repeat_branch(
&mut self,
block: BlockRef,
stop: Option<BlockRef>,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
let candidate = *self.branch_by_header.get(&block)?;
let loop_candidate = self.loop_by_header.get(&block).copied()?;
if loop_candidate.kind_hint != LoopKindHint::RepeatLike {
return None;
}
let merge = candidate.merge?;
if self
.lowering
.structure
.short_circuit_candidates
.iter()
.any(|short| short.reducible && short.header == block)
{
return None;
}
if self.active_loops.last().is_some_and(|loop_context| {
loop_context.continue_target.is_none() && loop_context.post_loop == merge
}) {
return None;
}
let tail = self.single_pass_repeat_tail(block, candidate)?;
if Some(tail) == stop {
return None;
}
let region = self.branch_regions_by_header.get(&block).copied()?;
let loop_context = ActiveLoopContext {
header: block,
loop_blocks: region.structured_blocks.clone(),
post_loop: merge,
downstream_post_loop: None,
continue_target: None,
continue_sources: BTreeSet::new(),
break_exits: BTreeMap::new(),
state_slots: Vec::new(),
};
self.active_loops.push(loop_context);
let body_result = self.lower_region_with_suppressed_loop(
block,
Some(tail),
target_overrides,
Some(block),
);
self.active_loops.pop();
let mut body = body_result?.stmts;
let tail_preds = BTreeSet::from([tail]);
let tail_target_overrides =
self.branch_value_target_overrides_for_preds(block, &tail_preds, target_overrides);
body.extend(self.lower_block_prefix(tail, false, &tail_target_overrides)?);
self.visited.insert(tail);
stmts.push(HirStmt::Repeat(Box::new(HirRepeat {
body: HirBlock { stmts: body },
cond: HirExpr::Boolean(true),
})));
Some(Some(merge))
}
fn single_pass_repeat_tail(
&self,
block: BlockRef,
candidate: &BranchCandidate,
) -> Option<BlockRef> {
let merge = candidate.merge?;
let region = self.branch_regions_by_header.get(&block).copied()?;
region
.structured_blocks
.iter()
.copied()
.filter(|tail| {
*tail != block
&& *tail != merge
&& !self.required_labels.contains(tail)
&& !self.branch_by_header.contains_key(tail)
&& !self.loop_by_header.contains_key(tail)
&& self.linear_tail_target(*tail) == Some(merge)
&& self.region_predecessor_count(*tail, ®ion.structured_blocks) >= 2
&& self.branch_arm_reaches_target_or_boundary(
candidate.then_entry,
*tail,
merge,
)
&& candidate.else_entry.is_none_or(|else_entry| {
self.branch_arm_reaches_target_or_boundary(else_entry, *tail, merge)
})
})
.min()
}
fn linear_tail_target(&self, block: BlockRef) -> Option<BlockRef> {
if matches!(
self.block_terminator(block)
.map(|(_instr_ref, instr)| instr),
Some(
LowInstr::Branch(_)
| LowInstr::NumericForInit(_)
| LowInstr::NumericForLoop(_)
| LowInstr::GenericForLoop(_)
| LowInstr::Return(_)
| LowInstr::TailCall(_)
)
) {
return None;
}
self.lowering.cfg.unique_reachable_successor(block)
}
fn region_predecessor_count(&self, block: BlockRef, region: &BTreeSet<BlockRef>) -> usize {
self.lowering.cfg.preds[block.index()]
.iter()
.filter(|edge_ref| region.contains(&self.lowering.cfg.edges[edge_ref.index()].from))
.count()
}
fn branch_arm_reaches_target_or_boundary(
&self,
entry: BlockRef,
target: BlockRef,
boundary: BlockRef,
) -> bool {
fn visit(
lowerer: &StructuredBodyLowerer<'_, '_>,
block: BlockRef,
target: BlockRef,
boundary: BlockRef,
visiting: &mut BTreeSet<BlockRef>,
memo: &mut BTreeMap<BlockRef, bool>,
) -> bool {
if block == target || block == boundary {
return true;
}
if block == lowerer.lowering.cfg.exit_block || lowerer.block_is_terminal_exit(block) {
return true;
}
if !lowerer.lowering.cfg.reachable_blocks.contains(&block) {
return false;
}
if let Some(result) = memo.get(&block).copied() {
return result;
}
if !visiting.insert(block) {
return true;
}
let result = lowerer.lowering.cfg.succs[block.index()]
.iter()
.all(|edge_ref| {
let successor = lowerer.lowering.cfg.edges[edge_ref.index()].to;
visit(lowerer, successor, target, boundary, visiting, memo)
});
visiting.remove(&block);
memo.insert(block, result);
result
}
visit(
self,
entry,
target,
boundary,
&mut BTreeSet::new(),
&mut BTreeMap::new(),
)
}
fn block_has_unstructured_continue_requirement(&self, block: BlockRef) -> bool {
self.lowering
.structure
.goto_requirements
.iter()
.any(|requirement| {
requirement.from == block
&& requirement.reason == GotoReason::UnstructuredContinueLike
})
}
fn entry_must_reach_or_escape_before_boundary(
&self,
entry: BlockRef,
target: BlockRef,
boundary: BlockRef,
) -> bool {
let boundary_is_loop_escape = self.active_loops.last().is_some_and(|loop_context| {
(loop_context.continue_target == Some(boundary)
&& self.loop_continue_target_is_empty(boundary))
|| loop_context.post_loop == boundary
|| loop_context.downstream_post_loop == Some(boundary)
});
fn visit(
lowerer: &StructuredBodyLowerer<'_, '_>,
block: BlockRef,
target: BlockRef,
boundary: BlockRef,
boundary_is_loop_escape: bool,
visiting: &mut BTreeSet<BlockRef>,
memo: &mut BTreeMap<BlockRef, bool>,
) -> bool {
if block == target {
return true;
}
if block == boundary {
return boundary_is_loop_escape;
}
if !lowerer.lowering.cfg.reachable_blocks.contains(&block) {
return false;
}
if block == lowerer.lowering.cfg.exit_block || lowerer.block_is_terminal_exit(block) {
return true;
}
if let Some(result) = memo.get(&block).copied() {
return result;
}
if !visiting.insert(block) {
return false;
}
let result = lowerer.lowering.cfg.succs[block.index()]
.iter()
.all(|edge_ref| {
let successor = lowerer.lowering.cfg.edges[edge_ref.index()].to;
visit(
lowerer,
successor,
target,
boundary,
boundary_is_loop_escape,
visiting,
memo,
)
});
visiting.remove(&block);
memo.insert(block, result);
result
}
visit(
self,
entry,
target,
boundary,
boundary_is_loop_escape,
&mut BTreeSet::new(),
&mut BTreeMap::new(),
)
}
fn lower_shared_continuation_branch(
&mut self,
shared: SharedContinuationBranch,
plan: &StructuredBranchPlan,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
for header in &plan.consumed_headers {
self.visited.insert(*header);
}
let gated_block = self.lower_region(
shared.gated_entry,
Some(shared.shared_entry),
target_overrides,
)?;
let mut cond = if shared.negate_cond {
plan.cond.clone().negate()
} else {
plan.cond.clone()
};
rewrite_expr_temps(&mut cond, &temp_expr_overrides(target_overrides));
stmts.push(branch_stmt(cond, gated_block, None));
Some(Some(shared.shared_entry))
}
fn try_lower_branch_exit_value_assignment(
&mut self,
block: BlockRef,
stop: Option<BlockRef>,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
let stop = stop?;
if target_overrides.is_empty() {
return None;
}
let short = self
.lowering
.structure
.short_circuit_candidates
.iter()
.find(|candidate| {
candidate.header == block
&& candidate.reducible
&& matches!(candidate.exit, ShortCircuitExit::BranchExit { .. })
})?;
let ShortCircuitExit::BranchExit { truthy, falsy } = short.exit else {
return None;
};
let (value_leaf, negate_cond) = if falsy == stop {
(truthy, false)
} else if truthy == stop {
(falsy, true)
} else {
return None;
};
if short.blocks.contains(&value_leaf)
|| self.branch_by_header.contains_key(&value_leaf)
|| self.loop_by_header.contains_key(&value_leaf)
{
return None;
}
let value_stmts = self.lower_block_prefix(value_leaf, false, target_overrides)?;
if !branch_exit_value_assignment_leaf_stmts_are_safe(&value_stmts, target_overrides) {
return None;
}
let allowed_blocks = BTreeSet::from([block]);
let decision = build_branch_decision_expr_mixed_eval(
self.lowering,
short,
short.entry,
&allowed_blocks,
)?;
let mut cond = finalize_condition_decision_expr(decision);
let condition_expr_overrides =
self.branch_exit_condition_expr_overrides(short, target_overrides)?;
rewrite_expr_temps(&mut cond, &condition_expr_overrides);
if expr_references_forbidden_candidate_temps(self.lowering, short, &cond, &allowed_blocks) {
return None;
}
if negate_cond {
cond = cond.negate();
}
rewrite_expr_temps(&mut cond, &temp_expr_overrides(target_overrides));
stmts.extend(self.lower_block_prefix(block, true, target_overrides)?);
self.visited.extend(short.blocks.iter().copied());
self.visited.insert(value_leaf);
stmts.push(branch_stmt(cond, HirBlock { stmts: value_stmts }, None));
Some(Some(stop))
}
fn branch_exit_condition_expr_overrides(
&self,
short: &ShortCircuitCandidate,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<BTreeMap<TempId, HirExpr>> {
let mut expr_overrides = BTreeMap::new();
for block in &short.blocks {
let prefix = self.lower_block_prefix(*block, true, target_overrides)?;
branch_exit_condition_prefix_expr_overrides(&prefix, &mut expr_overrides)?;
}
Some(expr_overrides)
}
fn try_lower_terminal_else_guard_branch(
&mut self,
block: BlockRef,
stop: Option<BlockRef>,
stmts: &mut Vec<HirStmt>,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<Option<BlockRef>> {
let stop = stop?;
let plan = self.build_plain_branch_plan(block)?;
let merge = plan.merge?;
if plan.else_entry.is_some()
|| plan.consumed_headers.len() != 1
|| !self.block_is_terminal_exit(merge)
|| !self.can_reach_avoiding_block(plan.then_entry, stop, merge)
{
return None;
}
let terminal_block = self.lower_terminal_exit_block_clone(merge, target_overrides)?;
stmts.extend(self.lower_block_prefix(block, true, target_overrides)?);
self.visited.insert(block);
self.visited.insert(merge);
let mut cond = plan.cond.negate();
rewrite_expr_temps(&mut cond, &temp_expr_overrides(target_overrides));
stmts.push(branch_stmt(cond, terminal_block, None));
Some(Some(plan.then_entry))
}
pub(super) fn lower_terminal_exit_block_clone(
&self,
block: BlockRef,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<HirBlock> {
if !self.block_is_terminal_exit(block) {
return None;
}
let mut stmts = self.lower_block_prefix(block, false, target_overrides)?;
let (instr_ref, instr) = self.block_terminator(block)?;
let empty_labels = BTreeMap::new();
let mut lowered =
lower_control_instr(self.lowering, block, instr_ref, instr, &empty_labels);
apply_loop_rewrites(&mut lowered, target_overrides);
if let Some(entry_expr_overrides) = self.block_entry_expr_overrides(block) {
for stmt in &mut lowered {
rewrite_stmt_exprs(stmt, entry_expr_overrides);
}
}
stmts.extend(lowered);
Some(HirBlock { stmts })
}
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;
}
if let Some(bvm) = self.branch_value_merges_by_header.get(&block)
&& bvm
.values
.iter()
.any(|v| Some(v.phi_id) != short.result_phi_id)
{
return None;
}
let plan = build_conditional_reassign_plan(self.lowering, block)?;
if let Some(stop) = stop
&& stop != merge
&& short.blocks.contains(&stop)
{
return None;
}
if value_merge_defs_are_overridden(self.lowering, short, target_overrides) {
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;
};
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 outputs = self.statement_value_merge_outputs(short)?;
let mut short_stmts = self.lower_block_prefix(block, true, target_overrides)?;
short_stmts.extend(
self.lower_value_merge_node(short, short.entry, &outputs, true, target_overrides)?
.stmts,
);
self.visited.insert(block);
self.visited.extend(value_merge_skipped_blocks(short));
for (output_short, _) in &outputs {
self.overrides.suppress_phi(output_short.result_phi_id?);
}
stmts.extend(short_stmts);
if let Some(bvm) = self.branch_value_merges_by_header.get(&block) {
for value in &bvm.values {
if Some(value.phi_id) == short.result_phi_id {
continue;
}
if let Some(decision_expr) =
self.build_secondary_value_merge_decision(short, value.reg)
{
let bvm_temp = self.lowering.bindings.phi_temps[value.phi_id.index()];
let mut stmt =
assign_stmt(vec![HirLValue::Temp(bvm_temp)], vec![decision_expr]);
apply_loop_rewrites(std::slice::from_mut(&mut stmt), target_overrides);
stmts.push(stmt);
self.overrides.suppress_phi(value.phi_id);
}
}
}
Some(Some(merge))
}
fn statement_value_merge_outputs(
&self,
short: &'b ShortCircuitCandidate,
) -> Option<Vec<StatementValueMergeOutput<'b>>> {
let mut outputs = Vec::new();
for candidate in &self.lowering.structure.short_circuit_candidates {
if !same_statement_value_merge_tree(short, candidate) {
continue;
}
let temp = *self
.lowering
.bindings
.phi_temps
.get(candidate.result_phi_id?.index())?;
outputs.push((candidate, temp));
}
(!outputs.is_empty()).then_some(outputs)
}
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 let Some(bvm) = self.branch_value_merges_by_header.get(&block)
&& bvm
.values
.iter()
.any(|v| Some(v.phi_id) != short.result_phi_id)
{
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)?;
if candidate.else_entry.is_some()
&& loop_context.continue_target == Some(candidate.then_entry)
{
return None;
}
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 && 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 self.block_exits_outer_active_loop(merge)
&& (candidate.then_entry == continue_target
|| self
.lowering
.cfg
.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
.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?;
if !self.loop_continue_target_is_empty(continue_target) {
return None;
}
if self
.try_build_short_circuit_plan(block, stop)
.flatten()
.is_some()
{
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,
);
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 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 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)?;
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 {
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
.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,
outputs: &[StatementValueMergeOutput<'_>],
prefix_emitted: bool,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> 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, target_overrides)?);
}
let mut cond = lower_short_circuit_subject(self.lowering, node.header)?;
rewrite_expr_temps(&mut cond, &temp_expr_overrides(target_overrides));
let truthy = self.lower_value_merge_target(
short,
node.header,
&node.truthy,
outputs,
target_overrides,
)?;
let falsy = self.lower_value_merge_target(
short,
node.header,
&node.falsy,
outputs,
target_overrides,
)?;
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,
outputs: &[StatementValueMergeOutput<'_>],
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<HirBlock> {
match target {
ShortCircuitTarget::Node(next_ref) => {
self.lower_value_merge_node(short, *next_ref, outputs, false, target_overrides)
}
ShortCircuitTarget::Value(block) => {
self.lower_value_merge_leaf(current_header, *block, outputs, target_overrides)
}
ShortCircuitTarget::TruthyExit | ShortCircuitTarget::FalsyExit => None,
}
}
fn lower_value_merge_leaf(
&self,
current_header: BlockRef,
block: BlockRef,
outputs: &[StatementValueMergeOutput<'_>],
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> Option<HirBlock> {
let mut stmts = if block == current_header {
Vec::new()
} else {
self.lower_block_prefix(block, false, target_overrides)?
};
for (short, target_temp) in outputs {
let value = if block == current_header
&& header_subject_is_value_carrier(self.lowering, current_header, short.result_reg)
{
lower_short_circuit_subject(self.lowering, block)?
} else {
lower_materialized_value_leaf_expr(self.lowering, short, block)?
};
let mut stmt = assign_stmt(vec![HirLValue::Temp(*target_temp)], vec![value]);
apply_loop_rewrites(std::slice::from_mut(&mut stmt), target_overrides);
stmts.push(stmt);
}
Some(HirBlock { stmts })
}
fn build_secondary_value_merge_decision(
&self,
short: &ShortCircuitCandidate,
reg: Reg,
) -> Option<HirExpr> {
let mut nodes = Vec::new();
self.build_secondary_decision_node(short, short.entry, reg, &mut nodes)?;
Some(HirExpr::Decision(Box::new(HirDecisionExpr {
entry: HirDecisionNodeRef(0),
nodes,
})))
}
fn build_secondary_decision_node(
&self,
short: &ShortCircuitCandidate,
node_ref: ShortCircuitNodeRef,
reg: Reg,
nodes: &mut Vec<HirDecisionNode>,
) -> Option<HirDecisionNodeRef> {
let node = short.nodes.get(node_ref.index())?;
let my_ref = HirDecisionNodeRef(nodes.len());
nodes.push(HirDecisionNode {
id: my_ref,
test: HirExpr::Nil,
truthy: HirDecisionTarget::CurrentValue,
falsy: HirDecisionTarget::CurrentValue,
});
let cond = lower_short_circuit_subject(self.lowering, node.header)?;
let truthy = self.build_secondary_decision_target(short, &node.truthy, reg, nodes)?;
let falsy = self.build_secondary_decision_target(short, &node.falsy, reg, nodes)?;
nodes[my_ref.index()].test = cond;
nodes[my_ref.index()].truthy = truthy;
nodes[my_ref.index()].falsy = falsy;
Some(my_ref)
}
fn build_secondary_decision_target(
&self,
short: &ShortCircuitCandidate,
target: &ShortCircuitTarget,
reg: Reg,
nodes: &mut Vec<HirDecisionNode>,
) -> Option<HirDecisionTarget> {
match target {
ShortCircuitTarget::Node(next_ref) => {
let node_ref = self.build_secondary_decision_node(short, *next_ref, reg, nodes)?;
Some(HirDecisionTarget::Node(node_ref))
}
ShortCircuitTarget::Value(block) => {
let value = expr_for_reg_at_block_exit(self.lowering, *block, reg);
Some(HirDecisionTarget::Expr(value))
}
ShortCircuitTarget::TruthyExit | ShortCircuitTarget::FalsyExit => None,
}
}
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);
}
}
fn value_merge_defs_are_overridden(
lowering: &ProtoLowering<'_>,
short: &ShortCircuitCandidate,
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> bool {
if target_overrides.is_empty() {
return false;
}
let is_overridden = |def: &DefId| {
lowering
.bindings
.fixed_temps
.get(def.index())
.is_some_and(|temp| target_overrides.contains_key(temp))
};
short.entry_defs.iter().any(is_overridden)
|| short
.value_incomings
.iter()
.any(|inc| inc.defs.iter().any(is_overridden))
}
fn same_statement_value_merge_tree(
base: &ShortCircuitCandidate,
candidate: &ShortCircuitCandidate,
) -> bool {
if !base.reducible
|| !candidate.reducible
|| base.header != candidate.header
|| base.blocks != candidate.blocks
|| base.entry != candidate.entry
|| base.nodes.len() != candidate.nodes.len()
|| base.result_phi_id.is_none()
|| candidate.result_phi_id.is_none()
|| base.result_reg.is_none()
|| candidate.result_reg.is_none()
{
return false;
}
let (ShortCircuitExit::ValueMerge(base_merge), ShortCircuitExit::ValueMerge(candidate_merge)) =
(&base.exit, &candidate.exit)
else {
return false;
};
if base_merge != candidate_merge {
return false;
}
base.nodes
.iter()
.zip(&candidate.nodes)
.all(|(base, candidate)| {
base.id == candidate.id
&& base.header == candidate.header
&& base.truthy == candidate.truthy
&& base.falsy == candidate.falsy
})
}
fn branch_exit_value_assignment_leaf_stmts_are_safe(
stmts: &[HirStmt],
target_overrides: &BTreeMap<TempId, HirLValue>,
) -> bool {
let [HirStmt::Assign(assign)] = stmts else {
return false;
};
let [target] = assign.targets.as_slice() else {
return false;
};
let [value] = assign.values.as_slice() else {
return false;
};
if !branch_exit_value_assignment_leaf_value_is_safe(value) {
return false;
}
target_overrides
.values()
.any(|override_target| override_target == target)
}
fn branch_exit_value_assignment_leaf_value_is_safe(value: &HirExpr) -> bool {
matches!(
value,
HirExpr::Nil
| HirExpr::Boolean(_)
| HirExpr::Integer(_)
| HirExpr::Number(_)
| HirExpr::String(_)
| HirExpr::Int64(_)
| HirExpr::UInt64(_)
| HirExpr::ParamRef(_)
| HirExpr::LocalRef(_)
| HirExpr::UpvalueRef(_)
| HirExpr::TempRef(_)
| HirExpr::GlobalRef(_)
)
}
fn branch_exit_condition_prefix_expr_overrides(
stmts: &[HirStmt],
expr_overrides: &mut BTreeMap<TempId, HirExpr>,
) -> Option<()> {
for stmt in stmts {
let HirStmt::Assign(assign) = stmt else {
return None;
};
let [HirLValue::Temp(target)] = assign.targets.as_slice() else {
return None;
};
let [value] = assign.values.as_slice() else {
return None;
};
if !branch_exit_condition_prefix_expr_is_safe(value) {
continue;
}
let mut value = value.clone();
rewrite_expr_temps(&mut value, expr_overrides);
expr_overrides.insert(*target, value);
}
Some(())
}
fn branch_exit_condition_prefix_expr_is_safe(expr: &HirExpr) -> bool {
match expr {
HirExpr::Nil
| HirExpr::Boolean(_)
| HirExpr::Integer(_)
| HirExpr::Number(_)
| HirExpr::String(_)
| HirExpr::Int64(_)
| HirExpr::UInt64(_)
| HirExpr::ParamRef(_)
| HirExpr::LocalRef(_)
| HirExpr::UpvalueRef(_)
| HirExpr::TempRef(_)
| HirExpr::GlobalRef(_) => true,
HirExpr::TableAccess(access) => {
branch_exit_condition_prefix_expr_is_safe(&access.base)
&& branch_exit_condition_prefix_expr_is_safe(&access.key)
}
HirExpr::Unary(unary) => branch_exit_condition_prefix_expr_is_safe(&unary.expr),
HirExpr::Binary(binary) => {
branch_exit_condition_prefix_expr_is_safe(&binary.lhs)
&& branch_exit_condition_prefix_expr_is_safe(&binary.rhs)
}
HirExpr::LogicalAnd(logical) | HirExpr::LogicalOr(logical) => {
branch_exit_condition_prefix_expr_is_safe(&logical.lhs)
&& branch_exit_condition_prefix_expr_is_safe(&logical.rhs)
}
HirExpr::Call(_)
| HirExpr::VarArg
| HirExpr::TableConstructor(_)
| HirExpr::Closure(_)
| HirExpr::Decision(_)
| HirExpr::Unresolved(_)
| HirExpr::Complex { .. } => false,
}
}