use std::collections::BTreeSet;
use crate::cfg::{Cfg, EdgeKind};
use crate::transformer::{LowInstr, LoweredProto};
use super::common::IrreducibleRegion;
use super::common::{BranchRegionFact, GotoReason, GotoRequirement, LoopCandidate, LoopKindHint};
use super::helpers::{collect_region_entry_edges, collect_region_exit_edges};
pub(super) fn analyze_goto_requirements(
proto: &LoweredProto,
cfg: &Cfg,
loop_candidates: &[LoopCandidate],
branch_regions: &[BranchRegionFact],
irreducible_regions: &[IrreducibleRegion],
) -> Vec<GotoRequirement> {
let mut requirements = BTreeSet::new();
for loop_candidate in loop_candidates {
for edge_ref in collect_region_entry_edges(cfg, &loop_candidate.blocks) {
let edge = cfg.edges[edge_ref.index()];
if edge.to != loop_candidate.header {
requirements.insert(GotoRequirement {
from: edge.from,
to: edge.to,
reason: GotoReason::MultiEntryRegion,
});
}
}
if let Some(continue_target) = loop_candidate.continue_target {
let tail_carries_body = matches!(
loop_candidate.kind_hint,
LoopKindHint::NumericForLike | LoopKindHint::RepeatLike
) && block_has_non_control_prefix(proto, cfg, continue_target);
for block in &loop_candidate.blocks {
for edge_ref in &cfg.succs[block.index()] {
let edge = cfg.edges[edge_ref.index()];
if edge.to == continue_target
&& !tail_carries_body
&& !loop_candidate.backedges.contains(edge_ref)
&& edge.kind != EdgeKind::Fallthrough
&& cfg.reachable_blocks.contains(&edge.from)
{
requirements.insert(GotoRequirement {
from: edge.from,
to: edge.to,
reason: GotoReason::UnstructuredContinueLike,
});
}
}
}
}
}
for irreducible in irreducible_regions {
for edge_ref in &irreducible.entry_edges {
let edge = cfg.edges[edge_ref.index()];
requirements.insert(GotoRequirement {
from: edge.from,
to: edge.to,
reason: GotoReason::IrreducibleFlow,
});
}
}
for branch_region in branch_regions {
for edge_ref in collect_region_exit_edges(cfg, &branch_region.flow_blocks) {
let edge = cfg.edges[edge_ref.index()];
if edge.to != branch_region.merge {
requirements.insert(GotoRequirement {
from: edge.from,
to: edge.to,
reason: GotoReason::CrossStructureJump,
});
}
}
}
requirements.into_iter().collect()
}
fn block_has_non_control_prefix(
proto: &LoweredProto,
cfg: &Cfg,
block: crate::cfg::BlockRef,
) -> bool {
let range = cfg.blocks[block.index()].instrs;
let Some(last_instr_ref) = range.last() else {
return false;
};
let Some(last_instr) = proto.instrs.get(last_instr_ref.index()) else {
return false;
};
let body_end = if is_control_terminator(last_instr) {
range.end().saturating_sub(1)
} else {
range.end()
};
range.start.index() < body_end
}
fn is_control_terminator(instr: &LowInstr) -> bool {
matches!(
instr,
LowInstr::Jump(_)
| LowInstr::Branch(_)
| LowInstr::Return(_)
| LowInstr::TailCall(_)
| LowInstr::NumericForInit(_)
| LowInstr::NumericForLoop(_)
| LowInstr::GenericForLoop(_)
)
}