plotnik_lib/query/
graph_optimize.rs1use std::collections::{HashMap, HashSet};
14
15use crate::ir::{Nav, NavKind};
16
17use super::Query;
18use super::graph::{BuildGraph, BuildMatcher, NodeId};
19
20#[derive(Debug, Default)]
22pub struct OptimizeStats {
23 pub epsilons_eliminated: usize,
24 pub epsilons_kept: usize,
25}
26
27impl Query<'_> {
28 pub(super) fn optimize_graph(&mut self) {
32 let (dead, _stats) = eliminate_epsilons(&mut self.graph);
33 self.dead_nodes = dead;
34 }
35}
36
37pub fn eliminate_epsilons(graph: &mut BuildGraph) -> (HashSet<NodeId>, OptimizeStats) {
41 let mut stats = OptimizeStats::default();
42 let mut dead_nodes: HashSet<NodeId> = HashSet::new();
43
44 let mut predecessors = build_predecessor_map(graph);
45
46 let node_count = graph.len() as NodeId;
48 for id in (0..node_count).rev() {
49 if dead_nodes.contains(&id) {
50 continue;
51 }
52
53 let node = graph.node(id);
54 if !is_eliminable_epsilon(node, graph, &predecessors) {
55 if node.is_epsilon() {
56 stats.epsilons_kept += 1;
57 }
58 continue;
59 }
60
61 let successor_id = node.successors[0];
62 let effects_to_prepend = graph.node(id).effects.clone();
63 let nav_to_transfer = graph.node(id).nav;
64 let preds = predecessors.get(&id).cloned().unwrap_or_default();
65
66 if !effects_to_prepend.is_empty() {
68 let succ = graph.node_mut(successor_id);
69 let mut new_effects = effects_to_prepend;
70 new_effects.append(&mut succ.effects);
71 succ.effects = new_effects;
72 }
73
74 let successor_nav = graph.node(successor_id).nav;
76 if !nav_to_transfer.is_stay() {
77 if successor_nav.is_stay() {
78 graph.node_mut(successor_id).nav = nav_to_transfer;
79 } else if can_merge_up(nav_to_transfer, successor_nav) {
80 let merged = Nav::up(nav_to_transfer.level + successor_nav.level);
81 graph.node_mut(successor_id).nav = merged;
82 }
83 }
84
85 for pred_id in &preds {
87 if dead_nodes.contains(pred_id) {
88 continue;
89 }
90 let pred = graph.node_mut(*pred_id);
91 for succ in &mut pred.successors {
92 if *succ == id {
93 *succ = successor_id;
94 }
95 }
96 predecessors.entry(successor_id).or_default().push(*pred_id);
98 }
99 if let Some(succ_preds) = predecessors.get_mut(&successor_id) {
101 succ_preds.retain(|&p| p != id);
102 }
103
104 redirect_definitions(graph, id, successor_id);
105
106 dead_nodes.insert(id);
107 stats.epsilons_eliminated += 1;
108 }
109
110 (dead_nodes, stats)
111}
112
113fn is_eliminable_epsilon(
114 node: &super::graph::BuildNode,
115 graph: &BuildGraph,
116 predecessors: &HashMap<NodeId, Vec<NodeId>>,
117) -> bool {
118 if !matches!(node.matcher, BuildMatcher::Epsilon) {
119 return false;
120 }
121
122 if node.ref_marker.is_some() {
123 return false;
124 }
125
126 if node.successors.len() != 1 {
127 return false;
128 }
129
130 let successor_id = node.successors[0];
131 let successor = graph.node(successor_id);
132
133 if !node.nav.is_stay() && !successor.nav.is_stay() && !can_merge_up(node.nav, successor.nav) {
134 return false;
135 }
136
137 if !node.nav.is_stay() {
140 let succ_pred_count = predecessors.get(&successor_id).map_or(0, |p| p.len());
141 if succ_pred_count > 1 {
142 return false;
143 }
144 }
145
146 if !node.effects.is_empty() && successor.ref_marker.is_some() {
147 return false;
148 }
149
150 if !node.effects.is_empty() && !successor.nav.is_stay() {
154 return false;
155 }
156
157 if !node.effects.is_empty() {
160 let succ_pred_count = predecessors.get(&successor_id).map_or(0, |p| p.len());
161 if succ_pred_count > 1 {
162 return false;
163 }
164 }
165
166 true
167}
168
169fn build_predecessor_map(graph: &BuildGraph) -> HashMap<NodeId, Vec<NodeId>> {
170 let mut predecessors: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
171
172 for (id, node) in graph.iter() {
173 for &succ in &node.successors {
174 predecessors.entry(succ).or_default().push(id);
175 }
176 }
177
178 predecessors
179}
180
181fn can_merge_up(a: Nav, b: Nav) -> bool {
182 a.kind == NavKind::Up && b.kind == NavKind::Up
183}
184
185fn redirect_definitions(graph: &mut BuildGraph, old_id: NodeId, new_id: NodeId) {
186 let updates: Vec<_> = graph
187 .definitions()
188 .filter(|(_, entry)| *entry == old_id)
189 .map(|(name, _)| name)
190 .collect();
191
192 for name in updates {
193 graph.add_definition(name, new_id);
194 }
195}