1use crate::pass::FunctionPass;
11use llvm_analysis::UseDefInfo;
12use llvm_ir::{Context, Function, InstrId, InstrKind, ValueRef};
13use std::collections::HashSet;
14
15pub struct DeadCodeElim;
17
18impl FunctionPass for DeadCodeElim {
19 fn name(&self) -> &'static str {
20 "dce"
21 }
22
23 fn run_on_function(&mut self, _ctx: &mut Context, func: &mut Function) -> bool {
24 let info = UseDefInfo::compute(func);
25
26 let dead: HashSet<InstrId> = func
27 .instructions
28 .iter()
29 .enumerate()
30 .filter_map(|(i, instr)| {
31 let iid = InstrId(i as u32);
32 if is_dce_safe(&instr.kind) && info.is_dead(ValueRef::Instruction(iid)) {
33 Some(iid)
34 } else {
35 None
36 }
37 })
38 .collect();
39
40 if dead.is_empty() {
41 return false;
42 }
43
44 for bb in &mut func.blocks {
45 bb.body.retain(|id| !dead.contains(id));
46 }
47 true
48 }
49}
50
51pub fn is_dce_safe(kind: &InstrKind) -> bool {
57 !matches!(
58 kind,
59 InstrKind::Alloca { .. }
60 | InstrKind::Load { .. }
61 | InstrKind::Store { .. }
62 | InstrKind::Call { .. }
63 | InstrKind::Ret { .. }
64 | InstrKind::Br { .. }
65 | InstrKind::CondBr { .. }
66 | InstrKind::Switch { .. }
67 | InstrKind::Unreachable
68 )
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::pass::FunctionPass;
75 use llvm_ir::{ArgId, Builder, Context, InstrKind, Linkage, Module, ValueRef};
76
77 fn make_dead_fn() -> (Context, Module) {
79 let mut ctx = Context::new();
80 let mut module = Module::new("test");
81 let mut b = Builder::new(&mut ctx, &mut module);
82 b.add_function(
83 "f",
84 b.ctx.i32_ty,
85 vec![b.ctx.i32_ty],
86 vec!["x".into()],
87 false,
88 Linkage::External,
89 );
90 let entry = b.add_block("entry");
91 b.position_at_end(entry);
92 let x = b.get_arg(0);
93 let _dead = b.build_add("dead", x, x); b.build_ret(x);
95 (ctx, module)
96 }
97
98 #[test]
99 fn dce_removes_dead_add() {
100 let (mut ctx, mut module) = make_dead_fn();
101 let func = &module.functions[0];
102 let body_before = func.blocks[0].body.len();
104 assert_eq!(body_before, 1, "one non-term instr (add)");
105
106 let mut pass = DeadCodeElim;
107 let changed = pass.run_on_function(&mut ctx, &mut module.functions[0]);
108 assert!(changed);
109 assert_eq!(
110 module.functions[0].blocks[0].body.len(),
111 0,
112 "add removed; body should be empty"
113 );
114 }
115
116 #[test]
117 fn dce_keeps_used_instr() {
118 let mut ctx = Context::new();
119 let mut module = Module::new("test");
120 let mut b = Builder::new(&mut ctx, &mut module);
121 b.add_function(
122 "g",
123 b.ctx.i32_ty,
124 vec![b.ctx.i32_ty, b.ctx.i32_ty],
125 vec!["a".into(), "b".into()],
126 false,
127 Linkage::External,
128 );
129 let entry = b.add_block("entry");
130 b.position_at_end(entry);
131 let a = b.get_arg(0);
132 let bv = b.get_arg(1);
133 let sum = b.build_add("sum", a, bv);
134 b.build_ret(sum); let mut pass = DeadCodeElim;
137 let changed = pass.run_on_function(&mut ctx, &mut module.functions[0]);
138 assert!(!changed);
139 assert_eq!(
140 module.functions[0].blocks[0].body.len(),
141 1,
142 "sum must remain"
143 );
144 }
145
146 #[test]
147 fn dce_safe_classification() {
148 assert!(!is_dce_safe(&InstrKind::Unreachable));
150 assert!(!is_dce_safe(&InstrKind::Ret { val: None }));
151 assert!(!is_dce_safe(&InstrKind::Store {
152 val: ValueRef::Argument(ArgId(0)),
153 ptr: ValueRef::Argument(ArgId(1)),
154 align: None,
155 volatile: false,
156 }));
157 assert!(is_dce_safe(&InstrKind::Add {
159 flags: llvm_ir::IntArithFlags::default(),
160 lhs: ValueRef::Argument(ArgId(0)),
161 rhs: ValueRef::Argument(ArgId(0)),
162 }));
163 }
164}