1use crate::occ::{get_occ, occ_analysis, Occ};
2use tidepool_eval::{Changed, Pass};
3use tidepool_repr::{get_children, replace_subtree, CoreExpr, CoreFrame};
4
5pub struct Dce;
9
10impl Pass for Dce {
11 fn run(&self, expr: &mut CoreExpr) -> Changed {
12 if expr.nodes.is_empty() {
13 return false;
14 }
15 let occ_map = occ_analysis(expr);
16 match try_dce(expr, &occ_map) {
17 Some(new_expr) => {
18 *expr = new_expr;
19 true
20 }
21 None => false,
22 }
23 }
24
25 fn name(&self) -> &str {
26 "Dce"
27 }
28}
29
30fn try_dce(expr: &CoreExpr, occ_map: &crate::occ::OccMap) -> Option<CoreExpr> {
31 try_dce_at(expr, expr.nodes.len() - 1, occ_map)
32}
33
34fn try_dce_at(expr: &CoreExpr, idx: usize, occ_map: &crate::occ::OccMap) -> Option<CoreExpr> {
35 match &expr.nodes[idx] {
36 CoreFrame::LetNonRec { binder, body, .. } => {
37 if get_occ(occ_map, *binder) == Occ::Dead {
38 let body_tree = expr.extract_subtree(*body);
40 Some(replace_subtree(expr, idx, &body_tree))
41 } else {
42 try_children(expr, idx, occ_map)
43 }
44 }
45 CoreFrame::LetRec { bindings, body } => {
46 let all_dead = bindings
47 .iter()
48 .all(|(binder, _)| get_occ(occ_map, *binder) == Occ::Dead);
49 if all_dead {
50 let body_tree = expr.extract_subtree(*body);
52 Some(replace_subtree(expr, idx, &body_tree))
53 } else {
54 try_children(expr, idx, occ_map)
55 }
56 }
57 _ => try_children(expr, idx, occ_map),
58 }
59}
60
61fn try_children(expr: &CoreExpr, idx: usize, occ_map: &crate::occ::OccMap) -> Option<CoreExpr> {
62 let children = get_children(&expr.nodes[idx]);
63 for child in children {
64 if let Some(result) = try_dce_at(expr, child, occ_map) {
65 return Some(result);
66 }
67 }
68 None
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use tidepool_eval::{eval, Env, VecHeap};
75 use tidepool_repr::{Literal, VarId};
76
77 fn tree(nodes: Vec<CoreFrame<usize>>) -> CoreExpr {
79 CoreExpr { nodes }
80 }
81
82 #[test]
84 fn test_dce_dead_let() {
85 let x = VarId(1);
86 let expr = tree(vec![
87 CoreFrame::Lit(Literal::LitInt(42)), CoreFrame::Lit(Literal::LitInt(0)), CoreFrame::LetNonRec {
90 binder: x,
91 rhs: 0,
92 body: 1,
93 }, ]);
95 let mut dce_expr = expr;
96 let changed = Dce.run(&mut dce_expr);
97 assert!(changed);
98 assert_eq!(dce_expr.nodes.len(), 1);
99 assert_eq!(dce_expr.nodes[0], CoreFrame::Lit(Literal::LitInt(0)));
100 }
101
102 #[test]
104 fn test_dce_live_let_preserved() {
105 let x = VarId(1);
106 let expr = tree(vec![
107 CoreFrame::Lit(Literal::LitInt(42)), CoreFrame::Var(x), CoreFrame::LetNonRec {
110 binder: x,
111 rhs: 0,
112 body: 1,
113 }, ]);
115 let mut dce_expr = expr.clone();
116 let changed = Dce.run(&mut dce_expr);
117 assert!(!changed);
118 assert_eq!(dce_expr, expr);
119 }
120
121 #[test]
123 fn test_dce_letrec_all_dead() {
124 let f = VarId(1);
125 let g = VarId(2);
126 let expr = tree(vec![
127 CoreFrame::Lit(Literal::LitInt(1)), CoreFrame::Lit(Literal::LitInt(2)), CoreFrame::Lit(Literal::LitInt(0)), CoreFrame::LetRec {
131 bindings: vec![(f, 0), (g, 1)],
132 body: 2,
133 }, ]);
135 let mut dce_expr = expr;
136 let changed = Dce.run(&mut dce_expr);
137 assert!(changed);
138 assert_eq!(dce_expr.nodes.len(), 1);
139 assert_eq!(dce_expr.nodes[0], CoreFrame::Lit(Literal::LitInt(0)));
140 }
141
142 #[test]
145 fn test_dce_letrec_one_live() {
146 let f = VarId(1);
147 let g = VarId(2);
148 let expr = tree(vec![
149 CoreFrame::Var(g), CoreFrame::Lit(Literal::LitInt(1)), CoreFrame::Var(f), CoreFrame::LetRec {
153 bindings: vec![(f, 0), (g, 1)],
154 body: 2,
155 }, ]);
157 let mut dce_expr = expr.clone();
158 let changed = Dce.run(&mut dce_expr);
159 assert!(!changed);
160 assert_eq!(dce_expr, expr);
161 }
162
163 #[test]
165 fn test_dce_nested() {
166 let x = VarId(1);
167 let y = VarId(2);
168 let expr = tree(vec![
169 CoreFrame::Lit(Literal::LitInt(42)), CoreFrame::Lit(Literal::LitInt(0)), CoreFrame::Var(x), CoreFrame::LetNonRec {
173 binder: y,
174 rhs: 1,
175 body: 2,
176 }, CoreFrame::LetNonRec {
178 binder: x,
179 rhs: 0,
180 body: 3,
181 }, ]);
183 let mut dce_expr = expr;
184 let changed = Dce.run(&mut dce_expr);
185 assert!(changed);
186 assert_eq!(dce_expr.nodes.len(), 3);
189 let root_idx = dce_expr.nodes.len() - 1;
191 if let CoreFrame::LetNonRec { binder, .. } = &dce_expr.nodes[root_idx] {
192 assert_eq!(*binder, x);
193 } else {
194 panic!(
195 "Root should be LetNonRec for x, got {:?}",
196 dce_expr.nodes[root_idx]
197 );
198 }
199 }
200
201 #[test]
203 fn test_dce_preserves_eval() {
204 let x = VarId(1);
205 let y = VarId(2);
206 let expr = tree(vec![
207 CoreFrame::Lit(Literal::LitInt(42)), CoreFrame::Lit(Literal::LitInt(99)), CoreFrame::Var(x), CoreFrame::LetNonRec {
211 binder: y,
212 rhs: 1,
213 body: 2,
214 }, CoreFrame::LetNonRec {
216 binder: x,
217 rhs: 0,
218 body: 3,
219 }, ]);
221 let mut dce_expr = expr.clone();
222
223 let mut heap = VecHeap::new();
224 let env = Env::new();
225
226 let val_orig = eval(&expr, &env, &mut heap).expect("Original eval failed");
227
228 let changed = Dce.run(&mut dce_expr);
229 assert!(changed);
230
231 let val_dce = eval(&dce_expr, &env, &mut heap).expect("DCE eval failed");
232
233 match (val_orig, val_dce) {
234 (tidepool_eval::Value::Lit(l1), tidepool_eval::Value::Lit(l2)) => assert_eq!(l1, l2),
235 _ => panic!("Expected literals"),
236 }
237 }
238}