1mod canonicalize;
2
3pub(crate) use canonicalize::{
4 canonicalize_control_flow_targets, remove_temp_control_flow_target_labels,
5};
6use {
7 crate::{ast::AST, astnode::ASTNode},
8 sbpf_analyze::remove_dead_functions,
9 sbpf_ir::{Cfg, InputNode, control_flow_graph},
10 std::collections::HashSet,
11};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum CfgDumpStage {
15 BeforeDfe,
16 AfterDfe,
17}
18
19impl CfgDumpStage {
20 pub fn file_name(self) -> &'static str {
21 match self {
22 Self::BeforeDfe => "dfe-before.dot",
23 Self::AfterDfe => "dfe-after.dot",
24 }
25 }
26}
27
28pub fn eliminate_unreachable_functions(ast: &mut AST) {
30 eliminate_unreachable_functions_with_observer(ast, |_, _| {});
31}
32
33pub fn eliminate_unreachable_functions_with_observer<F>(ast: &mut AST, mut observe: F)
36where
37 F: FnMut(CfgDumpStage, &Cfg),
38{
39 let mut cfg = cfg_for_ast(ast);
40 observe(CfgDumpStage::BeforeDfe, &cfg);
41
42 let removed_functions = remove_dead_functions(&mut cfg);
43
44 if !removed_functions.is_empty() {
45 let dead_node_ids: HashSet<usize> = removed_functions
46 .into_iter()
47 .flat_map(|f| f.node_ids)
48 .collect();
49 strip_dead_nodes(ast, &dead_node_ids);
50 }
51
52 assign_offsets(ast);
53
54 let cfg = cfg_for_ast(ast);
55 observe(CfgDumpStage::AfterDfe, &cfg);
56}
57
58fn strip_dead_nodes(ast: &mut AST, dead_node_ids: &HashSet<usize>) {
61 ast.nodes = std::mem::take(&mut ast.nodes)
62 .into_iter()
63 .enumerate()
64 .filter(|(idx, node)| {
65 !matches!(node, ASTNode::Label { .. } | ASTNode::Instruction { .. })
66 || !dead_node_ids.contains(idx)
67 })
68 .map(|(_, node)| node)
69 .collect();
70}
71
72pub fn assign_offsets(ast: &mut AST) {
76 let mut text_offset = 0u64;
77 let mut text_size = 0u64;
78 for node in &mut ast.nodes {
79 match node {
80 ASTNode::Label { offset, .. } => *offset = text_offset,
81 ASTNode::Instruction {
82 instruction,
83 offset,
84 } => {
85 *offset = text_offset;
86 let size = instruction.get_size();
87 text_offset += size;
88 text_size += size;
89 }
90 _ => {}
91 }
92 }
93 ast.set_text_size(text_size);
94}
95
96fn cfg_for_ast(ast: &AST) -> Cfg {
97 let function_entries = function_entries(ast);
98 let entry_label = ast.nodes.iter().find_map(|node| {
99 if let ASTNode::GlobalDecl { global_decl } = node {
100 Some(global_decl.entry_label.as_str())
101 } else {
102 None
103 }
104 });
105 let nodes = ast.nodes.iter().map(|node| match node {
106 ASTNode::Label { label, .. } => InputNode::Label(label.name.as_str()),
107 ASTNode::Instruction { instruction, .. } => InputNode::Instruction(instruction),
108 _ => InputNode::Other,
109 });
110 control_flow_graph(nodes, &function_entries, entry_label)
111}
112
113fn function_entries(ast: &AST) -> HashSet<String> {
114 ast.function_entries().clone()
115}
116
117#[cfg(test)]
118mod tests {
119 use {
120 super::*,
121 crate::astnode::{GlobalDecl, Label},
122 either::Either,
123 sbpf_common::{inst_param::Register, instruction::Instruction, opcode::Opcode},
124 };
125
126 #[test]
127 fn test_optimizer_preserves_unreachable_blocks_in_reachable_function() {
128 let mut ast = AST::new();
129 ast.add_function_entry("entrypoint".to_string());
130 ast.nodes = vec![
131 label_node("entrypoint", 0),
132 instruction_node(
133 Opcode::Ja,
134 None,
135 0,
136 Some(Either::Left("target".to_string())),
137 ),
138 instruction_node(Opcode::Mov64Imm, Some(0), 8, None),
139 label_node("target", 16),
140 instruction_node(Opcode::Exit, None, 16, None),
141 ];
142 ast.set_text_size(24);
143
144 eliminate_unreachable_functions(&mut ast);
145
146 assert_eq!(ast.nodes.len(), 5);
147 assert!(matches!(
148 &ast.nodes[1],
149 ASTNode::Instruction { instruction, offset }
150 if instruction.opcode == Opcode::Ja
151 && instruction.off == Some(Either::Left("target".to_string()))
152 && *offset == 0
153 ));
154 assert!(matches!(
155 &ast.nodes[2],
156 ASTNode::Instruction { instruction, offset }
157 if instruction.opcode == Opcode::Mov64Imm && *offset == 8
158 ));
159 assert!(matches!(
160 &ast.nodes[3],
161 ASTNode::Label { label, offset } if label.name == "target" && *offset == 16
162 ));
163 assert!(matches!(
164 &ast.nodes[4],
165 ASTNode::Instruction { instruction, offset }
166 if instruction.opcode == Opcode::Exit && *offset == 16
167 ));
168 }
169
170 #[test]
171 fn test_optimizer_removes_uncalled_function_only() {
172 let mut ast = AST::new();
173 ast.add_function_entry("entrypoint".to_string());
174 ast.add_function_entry("dead".to_string());
175 ast.add_function_entry("callee".to_string());
176 ast.nodes = vec![
177 label_node("entrypoint", 0),
178 call_node("callee", 0),
179 instruction_node(Opcode::Exit, None, 8, None),
180 label_node("dead", 16),
181 instruction_node(Opcode::Exit, None, 16, None),
182 label_node("callee", 24),
183 instruction_node(Opcode::Exit, None, 24, None),
184 ];
185 ast.set_text_size(32);
186
187 eliminate_unreachable_functions(&mut ast);
188
189 assert!(
190 !ast.nodes
191 .iter()
192 .any(|node| matches!(node, ASTNode::Label { label, .. } if label.name == "dead"))
193 );
194 assert!(
195 ast.nodes
196 .iter()
197 .any(|node| matches!(node, ASTNode::Label { label, offset }
198 if label.name == "callee" && *offset == 16))
199 );
200 assert_eq!(
201 ast.nodes
202 .iter()
203 .filter(|node| matches!(node, ASTNode::Instruction { .. }))
204 .count(),
205 3
206 );
207 }
208
209 #[test]
210 fn test_optimizer_uses_declared_entry_when_it_is_not_first() {
211 let mut ast = AST::new();
212 ast.add_function_entry("helper".to_string());
213 ast.add_function_entry("dead".to_string());
214 ast.add_function_entry("entrypoint".to_string());
215 ast.nodes = vec![
216 ASTNode::GlobalDecl {
217 global_decl: GlobalDecl {
218 entry_label: "entrypoint".to_string(),
219 span: 0..0,
220 },
221 },
222 label_node("helper", 0),
223 instruction_node(Opcode::Exit, None, 0, None),
224 label_node("dead", 8),
225 instruction_node(Opcode::Exit, None, 8, None),
226 label_node("entrypoint", 16),
227 call_node("helper", 16),
228 instruction_node(Opcode::Exit, None, 24, None),
229 ];
230 ast.set_text_size(32);
231
232 let cfg = cfg_for_ast(&ast);
233 assert_eq!(cfg.functions()[0].name(), "entrypoint");
234 assert_eq!(cfg.functions()[0].entry_block_id(), Some(2)); eliminate_unreachable_functions(&mut ast);
237
238 assert!(ast.nodes.iter().any(
239 |node| matches!(node, ASTNode::Label { label, .. } if label.name == "entrypoint")
240 ));
241 assert!(
242 ast.nodes
243 .iter()
244 .any(|node| matches!(node, ASTNode::Label { label, .. } if label.name == "helper"))
245 );
246 assert!(
247 !ast.nodes
248 .iter()
249 .any(|node| matches!(node, ASTNode::Label { label, .. } if label.name == "dead"))
250 );
251 }
252
253 #[test]
254 fn test_strip_dead_nodes_and_assign_offsets_recomputes_cumulatively() {
255 let mut ast = AST::new();
256 ast.nodes = vec![
258 label_node("entrypoint", 0),
259 instruction_node(Opcode::Exit, None, 0, None),
260 label_node("dead_a", 8),
261 instruction_node(Opcode::Exit, None, 8, None),
262 label_node("live", 16),
263 instruction_node(Opcode::Exit, None, 16, None),
264 label_node("dead_b", 24),
265 instruction_node(Opcode::Exit, None, 24, None),
266 label_node("target", 32),
267 instruction_node(Opcode::Exit, None, 32, None),
268 ];
269 ast.set_text_size(40);
270
271 strip_dead_nodes(&mut ast, &HashSet::from([2usize, 3, 6, 7]));
272 assign_offsets(&mut ast);
273
274 assert!(matches!(
275 &ast.nodes[2],
276 ASTNode::Label { label, offset } if label.name == "live" && *offset == 8
277 ));
278 assert!(matches!(
279 &ast.nodes[4],
280 ASTNode::Label { label, offset } if label.name == "target" && *offset == 16
281 ));
282 }
283
284 #[test]
285 fn test_assign_offsets_recomputes_from_scratch() {
286 let mut ast = AST::new();
287 ast.nodes = vec![
288 label_node("entrypoint", 999),
289 instruction_node(Opcode::Exit, None, 999, None),
290 label_node("next", 999),
291 instruction_node(Opcode::Exit, None, 999, None),
292 ];
293
294 assign_offsets(&mut ast);
295
296 assert!(matches!(
297 &ast.nodes[0],
298 ASTNode::Label { label, offset } if label.name == "entrypoint" && *offset == 0
299 ));
300 assert!(matches!(
301 &ast.nodes[1],
302 ASTNode::Instruction { offset, .. } if *offset == 0
303 ));
304 assert!(matches!(
305 &ast.nodes[2],
306 ASTNode::Label { label, offset } if label.name == "next" && *offset == 8
307 ));
308 }
309
310 fn label_node(name: &str, offset: u64) -> ASTNode {
311 ASTNode::Label {
312 label: Label {
313 name: name.to_string(),
314 span: 0..0,
315 },
316 offset,
317 }
318 }
319
320 fn instruction_node(
321 opcode: Opcode,
322 dst: Option<u8>,
323 offset: u64,
324 off: Option<Either<String, i16>>,
325 ) -> ASTNode {
326 ASTNode::Instruction {
327 instruction: Instruction {
328 opcode,
329 dst: dst.map(|n| Register { n }),
330 src: None,
331 off,
332 imm: None,
333 span: 0..0,
334 },
335 offset,
336 }
337 }
338
339 fn call_node(target: &str, offset: u64) -> ASTNode {
340 ASTNode::Instruction {
341 instruction: Instruction {
342 opcode: Opcode::Call,
343 dst: None,
344 src: None,
345 off: None,
346 imm: Some(Either::Left(target.to_string())),
347 span: 0..0,
348 },
349 offset,
350 }
351 }
352}