use vyre_foundation::ir::ProgramGraph;
use crate::{
candidate::CandidatePlan,
facts::{DataflowEdge, PlanningFacts},
legality::{analyze_fusion_pair, FusionDecision, FusionRejectionReason},
DependencyEdge, FusionGroupId, SearchBudget, SearchWork,
};
#[derive(Debug)]
pub(crate) struct RejectedEdge {
pub(crate) edge: DataflowEdge,
pub(crate) reason: FusionRejectionReason,
}
#[derive(Debug)]
pub(crate) struct SearchResult {
pub(crate) candidates: Vec<CandidatePlan>,
pub(crate) rejected: Vec<RejectedEdge>,
pub(crate) work: SearchWork,
}
pub(crate) fn explore(
graph: &ProgramGraph,
facts: &PlanningFacts,
dependencies: &[DependencyEdge],
budget: SearchBudget,
) -> SearchResult {
let mut candidates = vec![CandidatePlan::baseline(graph.nodes().len())];
let mut rejected = Vec::new();
let mut legal_edges = Vec::new();
let mut cpu_work = 0_u64;
for edge in &facts.dataflow {
if !can_spend(cpu_work, budget) {
break;
}
cpu_work = cpu_work.saturating_add(1);
match analyze_fusion_pair(graph, edge.from, edge.to, edge.value) {
FusionDecision::Legal => {
let candidate = CandidatePlan::from_edges(graph.nodes().len(), &[*edge]);
if candidate_is_acyclic(&candidate, dependencies) {
legal_edges.push(*edge);
if candidates.len() < budget.max_candidates as usize {
candidates.push(candidate);
}
} else {
rejected.push(RejectedEdge {
edge: *edge,
reason: FusionRejectionReason::DependencyCycle,
});
}
}
FusionDecision::Rejected(reason) => rejected.push(RejectedEdge {
edge: *edge,
reason,
}),
}
}
if legal_edges.len() > 1
&& candidates.len() < budget.max_candidates as usize
&& can_spend(cpu_work, budget)
{
cpu_work = cpu_work.saturating_add(1);
let mut accepted = Vec::new();
for edge in legal_edges {
let mut proposed = accepted.clone();
proposed.push(edge);
let candidate = CandidatePlan::from_edges(graph.nodes().len(), &proposed);
if candidate_is_acyclic(&candidate, dependencies) {
accepted = proposed;
} else {
rejected.push(RejectedEdge {
edge,
reason: FusionRejectionReason::DependencyCycle,
});
}
}
candidates.push(CandidatePlan::from_edges(graph.nodes().len(), &accepted));
}
candidates.sort_by(|left, right| left.node_groups.cmp(&right.node_groups));
candidates.dedup_by(|left, right| left.node_groups == right.node_groups);
SearchResult {
work: SearchWork {
candidates_explored: u32::try_from(candidates.len()).unwrap_or(u32::MAX),
cpu_work,
target_compilations: 0,
measurements: 0,
elapsed_ns: cpu_work.min(budget.max_elapsed_ns),
},
candidates,
rejected,
}
}
fn candidate_is_acyclic(candidate: &CandidatePlan, dependencies: &[DependencyEdge]) -> bool {
let groups = candidate
.node_groups
.iter()
.copied()
.map(FusionGroupId)
.collect::<Vec<_>>();
crate::group_stages(candidate.group_count(), dependencies, &groups).is_ok()
}
fn can_spend(cpu_work: u64, budget: SearchBudget) -> bool {
cpu_work < budget.max_cpu_work && cpu_work < budget.max_elapsed_ns
}