use crate::DeadCodeElimination;
use rlx_fusion::analysis::{Analysis, UseCounts};
use rlx_fusion::pass::Pass;
use rlx_ir::{Graph, NodeId, Op};
use std::collections::{HashMap, HashSet};
fn is_nondeterministic(op: &Op) -> bool {
op.is_nondeterministic()
}
fn is_leaf(op: &Op) -> bool {
op.is_leaf()
}
pub struct HoistSplit {
pub prepare: Graph,
pub main: Graph,
pub boundary: Vec<String>,
pub prepare_params: HashSet<String>,
pub main_params: HashSet<String>,
}
pub fn split_param_invariant(graph: &Graph) -> Option<HoistSplit> {
let mut dynamic: HashSet<NodeId> = HashSet::new();
for node in graph.nodes() {
let dyn_here = matches!(node.op, Op::Input { .. })
|| is_nondeterministic(&node.op)
|| node.inputs.iter().any(|i| dynamic.contains(i));
if dyn_here {
dynamic.insert(node.id);
}
}
let out_set: HashSet<NodeId> = graph.outputs.iter().copied().collect();
let uses = UseCounts::compute(graph);
let boundary_ids: Vec<NodeId> = graph
.nodes()
.iter()
.filter(|n| !dynamic.contains(&n.id) && !is_leaf(&n.op))
.filter(|n| out_set.contains(&n.id) || uses.users(n.id).iter().any(|u| dynamic.contains(u)))
.map(|n| n.id)
.collect();
if boundary_ids.is_empty() {
return None;
}
let boundary: Vec<String> = (0..boundary_ids.len())
.map(|i| format!("__rlx_prep_{i}"))
.collect();
let mut prepare = graph.clone();
prepare.set_outputs(boundary_ids.clone());
let prepare = DeadCodeElimination.run(prepare);
let boundary_name: HashMap<NodeId, &String> =
boundary_ids.iter().copied().zip(boundary.iter()).collect();
let mut main = Graph::new(&graph.name);
let mut id_map: HashMap<NodeId, NodeId> = HashMap::new();
for node in graph.nodes() {
let new_id = if let Some(name) = boundary_name.get(&node.id) {
main.input(name.as_str(), node.shape.clone())
} else {
let ins: Vec<NodeId> = node.inputs.iter().map(|i| id_map[i]).collect();
let nid = main.add_node(node.op.clone(), ins, node.shape.clone());
let src = graph.node(node.id);
let dst = main.node_mut(nid);
dst.name = src.name.clone();
dst.origin = src.origin.clone();
nid
};
id_map.insert(node.id, new_id);
}
let new_outputs: Vec<NodeId> = graph.outputs.iter().map(|o| id_map[o]).collect();
main.set_outputs(new_outputs);
let main = DeadCodeElimination.run(main);
let param_names = |g: &Graph| -> HashSet<String> {
g.nodes()
.iter()
.filter_map(|n| match &n.op {
Op::Param { name } => Some(name.clone()),
_ => None,
})
.collect()
};
let prepare_params = param_names(&prepare);
let main_params = param_names(&main);
Some(HoistSplit {
prepare,
main,
boundary,
prepare_params,
main_params,
})
}