1use crate::{
7 pass::PassManager, ConstProp, ConstantFold, DeadArgElim, DeadCodeElim, Gvn, Inliner, Ipcp,
8 LoopUnroll, Mem2Reg,
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum OptLevel {
14 O0,
16 O1,
18 O2,
20 O3,
22}
23
24impl OptLevel {
25 pub fn parse(s: &str) -> Option<Self> {
27 match s.trim().to_ascii_lowercase().as_str() {
28 "o0" | "0" => Some(Self::O0),
29 "o1" | "1" => Some(Self::O1),
30 "o2" | "2" => Some(Self::O2),
31 "o3" | "3" => Some(Self::O3),
32 _ => None,
33 }
34 }
35}
36
37pub fn build_pipeline(level: OptLevel) -> PassManager {
41 let mut pm = PassManager::new();
42
43 match level {
44 OptLevel::O0 => {
45 }
47 OptLevel::O1 => {
48 pm.add_function_pass(Mem2Reg);
49 pm.add_function_pass(ConstantFold);
50 pm.add_function_pass(ConstProp);
51 pm.add_function_pass(DeadCodeElim);
52 }
53 OptLevel::O2 => {
54 pm.add_function_pass(Mem2Reg);
55 pm.add_module_pass(Inliner::default());
56 pm.add_function_pass(Gvn);
57 pm.add_function_pass(LoopUnroll::default());
58 pm.add_function_pass(ConstantFold);
59 pm.add_function_pass(ConstProp);
60 pm.add_function_pass(DeadCodeElim);
61 pm.add_function_pass(Gvn);
63 pm.add_function_pass(ConstantFold);
64 pm.add_function_pass(ConstProp);
65 pm.add_function_pass(DeadCodeElim);
66 }
67 OptLevel::O3 => {
68 pm.add_function_pass(Mem2Reg);
69 pm.add_module_pass(Ipcp);
70 pm.add_module_pass(DeadArgElim);
71 pm.add_module_pass(Inliner {
72 size_limit: 100,
73 max_inline_depth: 16,
74 hot_loop_bonus: 100,
75 });
76 pm.add_function_pass(Gvn);
77 pm.add_function_pass(LoopUnroll {
78 factor: 8,
79 max_trip_count: 16,
80 });
81 pm.add_function_pass(ConstantFold);
82 pm.add_function_pass(ConstProp);
83 pm.add_function_pass(DeadCodeElim);
84 pm.add_function_pass(Gvn);
86 pm.add_function_pass(ConstantFold);
87 pm.add_function_pass(ConstProp);
88 pm.add_function_pass(DeadCodeElim);
89 pm.add_function_pass(ConstantFold);
90 pm.add_function_pass(ConstProp);
91 pm.add_function_pass(DeadCodeElim);
92 }
93 }
94
95 pm
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use llvm_ir::{Builder, Context, InstrKind, Linkage, Module, ValueRef};
102 use llvm_ir_parser::parser::parse;
103
104 fn make_dead_code_fn() -> (Context, Module) {
105 let mut ctx = Context::new();
106 let mut module = Module::new("test");
107 let mut b = Builder::new(&mut ctx, &mut module);
108 b.add_function(
109 "main",
110 b.ctx.i32_ty,
111 vec![],
112 vec![],
113 false,
114 Linkage::External,
115 );
116 let entry = b.add_block("entry");
117 b.position_at_end(entry);
118
119 let c1 = b.const_int(b.ctx.i32_ty, 1);
120 let c2 = b.const_int(b.ctx.i32_ty, 2);
121 let c100 = b.const_int(b.ctx.i32_ty, 100);
122 let c7 = b.const_int(b.ctx.i32_ty, 7);
123
124 let dead1 = b.build_add("dead1", c1, c2);
125 let _dead2 = b.build_mul("dead2", dead1, c100);
126 b.build_ret(c7);
127
128 (ctx, module)
129 }
130
131 #[test]
132 fn o2_preserves_return_semantics_and_reduces_body_size_vs_o0() {
133 let (mut ctx_o0, mut m_o0) = make_dead_code_fn();
134 let (mut ctx_o2, mut m_o2) = make_dead_code_fn();
135
136 let mut pm_o0 = build_pipeline(OptLevel::O0);
137 let mut pm_o2 = build_pipeline(OptLevel::O2);
138 pm_o0.run_until_fixed_point(&mut ctx_o0, &mut m_o0, 3);
139 pm_o2.run_until_fixed_point(&mut ctx_o2, &mut m_o2, 8);
140
141 let f0 = &m_o0.functions[0];
142 let f2 = &m_o2.functions[0];
143
144 let o0_body_len = f0.blocks[0].body.len();
145 let o2_body_len = f2.blocks[0].body.len();
146 assert!(
147 o2_body_len < o0_body_len,
148 "O2 should reduce instruction count (o0={}, o2={})",
149 o0_body_len,
150 o2_body_len
151 );
152
153 let t0 = f0.blocks[0].terminator.expect("o0 terminator");
154 let t2 = f2.blocks[0].terminator.expect("o2 terminator");
155
156 match (&f0.instr(t0).kind, &f2.instr(t2).kind) {
157 (InstrKind::Ret { val: Some(v0) }, InstrKind::Ret { val: Some(v2) }) => {
158 assert_eq!(
159 *v0,
160 ValueRef::Constant(ctx_o0.const_int(ctx_o0.i32_ty, 7)),
161 "o0 should still return constant 7"
162 );
163 assert_eq!(
164 *v2,
165 ValueRef::Constant(ctx_o2.const_int(ctx_o2.i32_ty, 7)),
166 "o2 should return constant 7"
167 );
168 }
169 _ => panic!("expected both pipelines to end with ret i32 7"),
170 }
171 }
172
173 #[test]
174 fn parse_opt_level_variants() {
175 assert_eq!(OptLevel::parse("O0"), Some(OptLevel::O0));
176 assert_eq!(OptLevel::parse("1"), Some(OptLevel::O1));
177 assert_eq!(OptLevel::parse("o2"), Some(OptLevel::O2));
178 assert_eq!(OptLevel::parse(" 3 "), Some(OptLevel::O3));
179 assert_eq!(OptLevel::parse("Ox"), None);
180 }
181
182 #[test]
183 fn o1_folds_trivial_constant_expression() {
184 let mut ctx = Context::new();
185 let mut module = Module::new("fold");
186 let mut b = Builder::new(&mut ctx, &mut module);
187 b.add_function(
188 "main",
189 b.ctx.i32_ty,
190 vec![],
191 vec![],
192 false,
193 Linkage::External,
194 );
195 let entry = b.add_block("entry");
196 b.position_at_end(entry);
197 let c2 = b.const_int(b.ctx.i32_ty, 2);
198 let sum = b.build_add("sum", c2, c2);
199 b.build_ret(sum);
200
201 let mut pm = build_pipeline(OptLevel::O1);
202 pm.run_until_fixed_point(&mut ctx, &mut module, 4);
203 assert_eq!(module.functions[0].blocks[0].body.len(), 0);
204 let tid = module.functions[0].blocks[0].terminator.expect("ret");
205 match &module.functions[0].instr(tid).kind {
206 InstrKind::Ret {
207 val: Some(ValueRef::Constant(cid)),
208 } => match ctx.get_const(*cid) {
209 llvm_ir::ConstantData::Int { val, .. } => assert_eq!(*val, 4),
210 other => panic!("unexpected constant kind: {other:?}"),
211 },
212 other => panic!("expected constant return after O1, got {other:?}"),
213 }
214 }
215
216 fn parse_dbg_fixture() -> (Context, Module) {
217 let src = r#"
218source_filename = "pipeline_dbg.c"
219define i32 @f(i32 %a, i32 %b) {
220entry:
221 %s = add i32 %a, %b, !dbg !12
222 ret i32 %s, !dbg !13
223}
224!llvm.dbg.cu = !{!0}
225!0 = !DICompileUnit(language: DW_LANG_C99, file: !1)
226!1 = !DIFile(filename: "pipeline_dbg.c", directory: ".")
227!12 = !DILocation(line: 10, column: 2, scope: !0)
228!13 = !DILocation(line: 11, column: 3, scope: !0)
229"#;
230 parse(src).expect("parse debug fixture")
231 }
232
233 fn assert_debug_metadata_survives(module: &Module) {
234 let f = &module.functions[0];
235 assert!(
236 !f.instr_dbg_locs.is_empty(),
237 "function should retain at least one !dbg location"
238 );
239 assert!(
240 module
241 .named_metadata
242 .iter()
243 .any(|(k, _)| k == "llvm.dbg.cu"),
244 "named metadata llvm.dbg.cu must be preserved"
245 );
246
247 for loc_id in f.instr_dbg_locs.values() {
248 assert!(
249 module.debug_location(*loc_id).is_some(),
250 "missing debug location for !dbg !{}",
251 loc_id
252 );
253 }
254 }
255
256 #[test]
257 fn debug_metadata_survives_o1_o2_o3_pipelines() {
258 for level in [OptLevel::O1, OptLevel::O2, OptLevel::O3] {
259 let (mut ctx, mut module) = parse_dbg_fixture();
260 let mut pm = build_pipeline(level);
261 pm.run_until_fixed_point(&mut ctx, &mut module, 8);
262 assert_debug_metadata_survives(&module);
263 }
264 }
265}