use std::collections::{BTreeMap, BTreeSet};
use super::*;
impl StructuredBodyLowerer<'_, '_> {
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)
}
pub(super) fn branch_arm_reaches_shared_continuation_or_terminate(
&self,
entry: BlockRef,
continuation: BlockRef,
boundary: BlockRef,
) -> bool {
self.branch_arm_paths_all_match(entry, |block| {
if block == continuation {
return Some(true);
}
if block == boundary || !self.lowering.cfg.reachable_blocks.contains(&block) {
return Some(false);
}
(block == self.lowering.cfg.exit_block || self.block_is_terminal_exit(block))
.then_some(true)
})
}
pub(super) fn branch_arm_reaches_loop_continuation_or_escape(
&self,
entry: BlockRef,
continuation: BlockRef,
stop: BlockRef,
) -> bool {
self.branch_arm_paths_all_match(entry, |block| {
if block == continuation {
return Some(true);
}
if block == stop || block == self.lowering.cfg.exit_block {
return Some(false);
}
if self.block_is_active_loop_escape(block) {
return Some(true);
}
(!self.lowering.cfg.reachable_blocks.contains(&block)).then_some(false)
})
}
pub(super) fn branch_can_truncate_to_stop_or_loop_escape(
&self,
then_entry: BlockRef,
else_entry: Option<BlockRef>,
stop: BlockRef,
boundary: BlockRef,
) -> bool {
self.branch_arm_reaches_stop_or_loop_escape(then_entry, stop, boundary)
&& else_entry.is_none_or(|else_entry| {
self.branch_arm_reaches_stop_or_loop_escape(else_entry, stop, boundary)
})
}
pub(super) fn branch_arm_reaches_stop_or_loop_escape(
&self,
entry: BlockRef,
stop: BlockRef,
boundary: BlockRef,
) -> bool {
self.branch_arm_paths_all_match(entry, |block| {
if block == stop {
return Some(true);
}
if block == boundary {
return Some(self.block_is_active_loop_escape(block));
}
if block == self.lowering.cfg.exit_block || self.block_is_terminal_exit(block) {
return Some(true);
}
(!self.lowering.cfg.reachable_blocks.contains(&block)).then_some(false)
})
}
fn branch_arm_paths_all_match(
&self,
entry: BlockRef,
classify_boundary: impl Fn(BlockRef) -> Option<bool>,
) -> bool {
fn visit(
lowerer: &StructuredBodyLowerer<'_, '_>,
block: BlockRef,
classify_boundary: &impl Fn(BlockRef) -> Option<bool>,
visiting: &mut BTreeSet<BlockRef>,
memo: &mut BTreeMap<BlockRef, bool>,
) -> bool {
if let Some(result) = classify_boundary(block) {
return result;
}
if let Some(result) = memo.get(&block).copied() {
return result;
}
if let Some(loop_candidate) = lowerer.loop_by_header.get(&block).copied()
&& loop_candidate.reducible
{
let result = loop_candidate
.exits
.iter()
.all(|exit| visit(lowerer, *exit, classify_boundary, visiting, memo));
memo.insert(block, result);
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, classify_boundary, visiting, memo)
});
visiting.remove(&block);
memo.insert(block, result);
result
}
visit(
self,
entry,
&classify_boundary,
&mut BTreeSet::new(),
&mut BTreeMap::new(),
)
}
pub(super) fn block_is_active_loop_escape(&self, block: BlockRef) -> bool {
self.active_loops.last().is_some_and(|loop_context| {
loop_context.continue_target == Some(block)
|| loop_context.post_loop == block
|| loop_context.downstream_post_loop == Some(block)
|| loop_context.break_exits.contains_key(&block)
})
}
pub(super) fn block_exits_outer_active_loop(&self, block: BlockRef) -> bool {
self.active_loops.iter().rev().skip(1).any(|loop_context| {
loop_context.post_loop == block
|| loop_context.downstream_post_loop == Some(block)
|| loop_context.break_exits.contains_key(&block)
})
}
pub(super) fn loop_continue_target_is_empty(&self, block: BlockRef) -> bool {
if self.branch_by_header.contains_key(&block) {
return false;
}
let terminator = self.block_terminator(block).map(|(instr_ref, _)| instr_ref);
let range = self.lowering.cfg.blocks[block.index()].instrs;
(range.start.index()..range.end()).all(|instr_idx| {
let instr_ref = InstrRef(instr_idx);
Some(instr_ref) == terminator
|| self
.lowering
.proto
.instrs
.get(instr_idx)
.is_some_and(is_control_terminator)
})
}
pub(super) fn terminal_exit_block_is_clone_safe(&self, block: BlockRef) -> bool {
if !self.block_is_terminal_exit(block) {
return false;
}
let terminator = self.block_terminator(block).map(|(instr_ref, _)| instr_ref);
let range = self.lowering.cfg.blocks[block.index()].instrs;
(range.start.index()..range.end()).all(|instr_idx| {
let instr_ref = InstrRef(instr_idx);
Some(instr_ref) == terminator
|| !matches!(
self.lowering.proto.instrs.get(instr_idx),
Some(LowInstr::Closure(_))
)
})
}
pub(super) fn branch_can_truncate_to_stop(
&self,
block: BlockRef,
then_entry: BlockRef,
else_entry: Option<BlockRef>,
stop: BlockRef,
) -> bool {
let Some(region) = self.branch_regions_by_header.get(&block).copied() else {
return false;
};
if !region.structured_blocks.contains(&stop) {
return false;
}
let mut allowed_blocks = region.structured_blocks.clone();
allowed_blocks.insert(stop);
let arm_can_truncate_to_stop =
|entry| self.branch_arm_can_truncate_to_stop(entry, stop, &allowed_blocks);
arm_can_truncate_to_stop(then_entry) && else_entry.is_none_or(arm_can_truncate_to_stop)
}
fn branch_arm_can_truncate_to_stop(
&self,
entry: BlockRef,
stop: BlockRef,
allowed_blocks: &BTreeSet<BlockRef>,
) -> bool {
if entry == stop {
return true;
}
if self.branch_arm_crosses_live_continuation(entry, stop) {
return false;
}
self.lowering
.cfg
.can_reach_within(entry, stop, allowed_blocks)
|| self.branch_arm_terminates_before_stop(entry, stop)
}
fn branch_arm_crosses_live_continuation(&self, entry: BlockRef, stop: BlockRef) -> bool {
let mut visited = BTreeSet::new();
let mut stack = vec![entry];
while let Some(block) = stack.pop() {
if block == stop
|| block == self.lowering.cfg.exit_block
|| !self.lowering.cfg.reachable_blocks.contains(&block)
|| !visited.insert(block)
{
continue;
}
if self.can_reach(stop, block) && !self.block_is_terminal_exit(block) {
return true;
}
for edge_ref in &self.lowering.cfg.succs[block.index()] {
stack.push(self.lowering.cfg.edges[edge_ref.index()].to);
}
}
false
}
pub(super) fn branch_arm_terminates_before_stop(
&self,
entry: BlockRef,
stop: BlockRef,
) -> bool {
let mut visited = BTreeSet::new();
let mut stack = vec![entry];
let mut saw_terminal = false;
while let Some(block) = stack.pop() {
if block == stop || block == self.lowering.cfg.exit_block {
return true;
}
if !self.lowering.cfg.reachable_blocks.contains(&block) || !visited.insert(block) {
continue;
}
if self.block_is_terminal_exit(block) {
saw_terminal = true;
continue;
}
for edge_ref in &self.lowering.cfg.succs[block.index()] {
let successor = self.lowering.cfg.edges[edge_ref.index()].to;
if successor != stop {
stack.push(successor);
}
}
}
saw_terminal
}
pub(super) fn block_is_terminal_exit(&self, block: BlockRef) -> bool {
let succs = &self.lowering.cfg.succs[block.index()];
!succs.is_empty()
&& succs.iter().all(|edge_ref| {
let edge = self.lowering.cfg.edges[edge_ref.index()];
edge.to == self.lowering.cfg.exit_block
&& matches!(
edge.kind,
crate::structure::EdgeKind::Return | crate::structure::EdgeKind::TailCall
)
})
}
}