1use crate::constant_fold::try_fold;
16use crate::pass::FunctionPass;
17use llvm_ir::{Context, Function, InstrId, InstrKind, ValueRef};
18use std::collections::{HashMap, HashSet};
19
20pub struct ConstProp;
22
23impl FunctionPass for ConstProp {
24 fn name(&self) -> &'static str {
25 "const-prop"
26 }
27
28 fn run_on_function(&mut self, ctx: &mut Context, func: &mut Function) -> bool {
29 if func.blocks.is_empty() {
30 return false;
31 }
32
33 let mut subst: HashMap<InstrId, ValueRef> = HashMap::new();
35
36 for bi in rpo(func) {
38 let body: Vec<InstrId> = func.blocks[bi].body.clone();
39 for iid in body {
40 if !subst.is_empty() {
42 let new_kind = subst_kind(func.instr(iid).kind.clone(), &subst);
43 func.instr_mut(iid).kind = new_kind;
44 }
45 let kind = func.instr(iid).kind.clone();
47 if let Some(cid) = try_fold(ctx, &kind) {
48 subst.insert(iid, ValueRef::Constant(cid));
49 }
50 }
51 if let Some(tid) = func.blocks[bi].terminator {
53 if !subst.is_empty() {
54 let new_kind = subst_kind(func.instr(tid).kind.clone(), &subst);
55 func.instr_mut(tid).kind = new_kind;
56 }
57 }
58 }
59
60 if subst.is_empty() {
61 return false;
62 }
63
64 for bb in &mut func.blocks {
66 bb.body.retain(|id| !subst.contains_key(id));
67 }
68 true
69 }
70}
71
72pub(crate) fn subst_kind(kind: InstrKind, subst: &HashMap<InstrId, ValueRef>) -> InstrKind {
81 let s = |v: ValueRef| -> ValueRef {
82 if let ValueRef::Instruction(id) = v {
83 subst.get(&id).copied().unwrap_or(v)
84 } else {
85 v
86 }
87 };
88
89 match kind {
90 InstrKind::Add { flags, lhs, rhs } => InstrKind::Add {
92 flags,
93 lhs: s(lhs),
94 rhs: s(rhs),
95 },
96 InstrKind::Sub { flags, lhs, rhs } => InstrKind::Sub {
97 flags,
98 lhs: s(lhs),
99 rhs: s(rhs),
100 },
101 InstrKind::Mul { flags, lhs, rhs } => InstrKind::Mul {
102 flags,
103 lhs: s(lhs),
104 rhs: s(rhs),
105 },
106 InstrKind::UDiv { exact, lhs, rhs } => InstrKind::UDiv {
107 exact,
108 lhs: s(lhs),
109 rhs: s(rhs),
110 },
111 InstrKind::SDiv { exact, lhs, rhs } => InstrKind::SDiv {
112 exact,
113 lhs: s(lhs),
114 rhs: s(rhs),
115 },
116 InstrKind::URem { lhs, rhs } => InstrKind::URem {
117 lhs: s(lhs),
118 rhs: s(rhs),
119 },
120 InstrKind::SRem { lhs, rhs } => InstrKind::SRem {
121 lhs: s(lhs),
122 rhs: s(rhs),
123 },
124 InstrKind::And { lhs, rhs } => InstrKind::And {
126 lhs: s(lhs),
127 rhs: s(rhs),
128 },
129 InstrKind::Or { lhs, rhs } => InstrKind::Or {
130 lhs: s(lhs),
131 rhs: s(rhs),
132 },
133 InstrKind::Xor { lhs, rhs } => InstrKind::Xor {
134 lhs: s(lhs),
135 rhs: s(rhs),
136 },
137 InstrKind::Shl { flags, lhs, rhs } => InstrKind::Shl {
138 flags,
139 lhs: s(lhs),
140 rhs: s(rhs),
141 },
142 InstrKind::LShr { exact, lhs, rhs } => InstrKind::LShr {
143 exact,
144 lhs: s(lhs),
145 rhs: s(rhs),
146 },
147 InstrKind::AShr { exact, lhs, rhs } => InstrKind::AShr {
148 exact,
149 lhs: s(lhs),
150 rhs: s(rhs),
151 },
152 InstrKind::FAdd { flags, lhs, rhs } => InstrKind::FAdd {
154 flags,
155 lhs: s(lhs),
156 rhs: s(rhs),
157 },
158 InstrKind::FSub { flags, lhs, rhs } => InstrKind::FSub {
159 flags,
160 lhs: s(lhs),
161 rhs: s(rhs),
162 },
163 InstrKind::FMul { flags, lhs, rhs } => InstrKind::FMul {
164 flags,
165 lhs: s(lhs),
166 rhs: s(rhs),
167 },
168 InstrKind::FDiv { flags, lhs, rhs } => InstrKind::FDiv {
169 flags,
170 lhs: s(lhs),
171 rhs: s(rhs),
172 },
173 InstrKind::FRem { flags, lhs, rhs } => InstrKind::FRem {
174 flags,
175 lhs: s(lhs),
176 rhs: s(rhs),
177 },
178 InstrKind::FNeg { flags, operand } => InstrKind::FNeg {
179 flags,
180 operand: s(operand),
181 },
182 InstrKind::ICmp { pred, lhs, rhs } => InstrKind::ICmp {
184 pred,
185 lhs: s(lhs),
186 rhs: s(rhs),
187 },
188 InstrKind::FCmp {
189 flags,
190 pred,
191 lhs,
192 rhs,
193 } => InstrKind::FCmp {
194 flags,
195 pred,
196 lhs: s(lhs),
197 rhs: s(rhs),
198 },
199 InstrKind::Alloca {
201 alloc_ty,
202 num_elements,
203 align,
204 } => InstrKind::Alloca {
205 alloc_ty,
206 num_elements: num_elements.map(s),
207 align,
208 },
209 InstrKind::Load {
210 ty,
211 ptr,
212 align,
213 volatile,
214 } => InstrKind::Load {
215 ty,
216 ptr: s(ptr),
217 align,
218 volatile,
219 },
220 InstrKind::Store {
221 val,
222 ptr,
223 align,
224 volatile,
225 } => InstrKind::Store {
226 val: s(val),
227 ptr: s(ptr),
228 align,
229 volatile,
230 },
231 InstrKind::GetElementPtr {
232 inbounds,
233 base_ty,
234 ptr,
235 indices,
236 } => InstrKind::GetElementPtr {
237 inbounds,
238 base_ty,
239 ptr: s(ptr),
240 indices: indices.into_iter().map(s).collect(),
241 },
242 InstrKind::Trunc { val, to } => InstrKind::Trunc { val: s(val), to },
244 InstrKind::ZExt { val, to } => InstrKind::ZExt { val: s(val), to },
245 InstrKind::SExt { val, to } => InstrKind::SExt { val: s(val), to },
246 InstrKind::FPTrunc { val, to } => InstrKind::FPTrunc { val: s(val), to },
247 InstrKind::FPExt { val, to } => InstrKind::FPExt { val: s(val), to },
248 InstrKind::FPToUI { val, to } => InstrKind::FPToUI { val: s(val), to },
249 InstrKind::FPToSI { val, to } => InstrKind::FPToSI { val: s(val), to },
250 InstrKind::UIToFP { val, to } => InstrKind::UIToFP { val: s(val), to },
251 InstrKind::SIToFP { val, to } => InstrKind::SIToFP { val: s(val), to },
252 InstrKind::PtrToInt { val, to } => InstrKind::PtrToInt { val: s(val), to },
253 InstrKind::IntToPtr { val, to } => InstrKind::IntToPtr { val: s(val), to },
254 InstrKind::BitCast { val, to } => InstrKind::BitCast { val: s(val), to },
255 InstrKind::AddrSpaceCast { val, to } => InstrKind::AddrSpaceCast { val: s(val), to },
256 InstrKind::Freeze { val } => InstrKind::Freeze { val: s(val) },
257 InstrKind::Select {
259 cond,
260 then_val,
261 else_val,
262 } => InstrKind::Select {
263 cond: s(cond),
264 then_val: s(then_val),
265 else_val: s(else_val),
266 },
267 InstrKind::Phi { ty, incoming } => InstrKind::Phi {
268 ty,
269 incoming: incoming.into_iter().map(|(v, b)| (s(v), b)).collect(),
270 },
271 InstrKind::ExtractValue { aggregate, indices } => InstrKind::ExtractValue {
272 aggregate: s(aggregate),
273 indices,
274 },
275 InstrKind::InsertValue {
276 aggregate,
277 val,
278 indices,
279 } => InstrKind::InsertValue {
280 aggregate: s(aggregate),
281 val: s(val),
282 indices,
283 },
284 InstrKind::ExtractElement { vec, idx } => InstrKind::ExtractElement {
285 vec: s(vec),
286 idx: s(idx),
287 },
288 InstrKind::InsertElement { vec, val, idx } => InstrKind::InsertElement {
289 vec: s(vec),
290 val: s(val),
291 idx: s(idx),
292 },
293 InstrKind::ShuffleVector { v1, v2, mask } => InstrKind::ShuffleVector {
294 v1: s(v1),
295 v2: s(v2),
296 mask,
297 },
298 InstrKind::Call {
300 tail,
301 callee_ty,
302 callee,
303 args,
304 } => InstrKind::Call {
305 tail,
306 callee_ty,
307 callee: s(callee),
308 args: args.into_iter().map(s).collect(),
309 },
310 InstrKind::Ret { val } => InstrKind::Ret { val: val.map(s) },
312 InstrKind::Br { dest } => InstrKind::Br { dest },
313 InstrKind::CondBr {
314 cond,
315 then_dest,
316 else_dest,
317 } => InstrKind::CondBr {
318 cond: s(cond),
319 then_dest,
320 else_dest,
321 },
322 InstrKind::Switch {
323 val,
324 default,
325 cases,
326 } => InstrKind::Switch {
327 val: s(val),
328 default,
329 cases: cases.into_iter().map(|(v, b)| (s(v), b)).collect(),
330 },
331 InstrKind::Unreachable => InstrKind::Unreachable,
332 }
333}
334
335pub(crate) fn rpo(func: &Function) -> Vec<usize> {
350 let n = func.blocks.len();
351 let mut visited: HashSet<usize> = HashSet::with_capacity(n);
352 let mut post_order: Vec<usize> = Vec::with_capacity(n);
353
354 let mut stack: Vec<(usize, bool)> = vec![(0, false)]; while let Some((bi, post)) = stack.pop() {
357 if post {
358 post_order.push(bi);
359 continue;
360 }
361 if visited.contains(&bi) {
362 continue;
363 }
364 visited.insert(bi);
365 stack.push((bi, true));
368 if let Some(tid) = func.blocks[bi].terminator {
369 let succs = func.instr(tid).successors();
370 for &succ in succs.iter().rev() {
371 let si = succ.0 as usize;
372 if si < n && !visited.contains(&si) {
373 stack.push((si, false));
374 }
375 }
376 }
377 }
378
379 post_order.reverse();
381
382 for bi in 0..n {
384 if !visited.contains(&bi) {
385 post_order.push(bi);
386 }
387 }
388
389 post_order
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395 use crate::pass::FunctionPass;
396 use llvm_ir::{Builder, Context, Linkage, Module, ValueRef};
397
398 fn make_const_add_fn() -> (Context, Module) {
400 let mut ctx = Context::new();
401 let mut module = Module::new("test");
402 let mut b = Builder::new(&mut ctx, &mut module);
403 b.add_function("f", b.ctx.i32_ty, vec![], vec![], false, Linkage::External);
404 let entry = b.add_block("entry");
405 b.position_at_end(entry);
406 let c2 = b.const_int(b.ctx.i32_ty, 2);
407 let c3 = b.const_int(b.ctx.i32_ty, 3);
408 let sum = b.build_add("sum", c2, c3);
409 b.build_ret(sum);
410 (ctx, module)
411 }
412
413 #[test]
414 fn const_prop_folds_add() {
415 let (mut ctx, mut module) = make_const_add_fn();
416 assert_eq!(module.functions[0].blocks[0].body.len(), 1);
418
419 let mut pass = ConstProp;
420 let changed = pass.run_on_function(&mut ctx, &mut module.functions[0]);
421 assert!(changed);
422
423 assert_eq!(
425 module.functions[0].blocks[0].body.len(),
426 0,
427 "folded add must be removed from body"
428 );
429
430 let func = &module.functions[0];
432 let tid = func.blocks[0].terminator.unwrap();
433 if let llvm_ir::InstrKind::Ret { val: Some(v) } = &func.instr(tid).kind {
434 if let ValueRef::Constant(cid) = v {
435 assert_eq!(
436 ctx.get_const(*cid),
437 &llvm_ir::ConstantData::Int {
438 ty: ctx.i32_ty,
439 val: 5
440 }
441 );
442 } else {
443 panic!("ret operand should be a constant");
444 }
445 } else {
446 panic!("terminator should be ret with value");
447 }
448 }
449
450 #[test]
451 fn const_prop_chain() {
452 let mut ctx = Context::new();
454 let mut module = Module::new("test");
455 let mut b = Builder::new(&mut ctx, &mut module);
456 b.add_function("f", b.ctx.i32_ty, vec![], vec![], false, Linkage::External);
457 let entry = b.add_block("entry");
458 b.position_at_end(entry);
459 let c2 = b.const_int(b.ctx.i32_ty, 2);
460 let c3 = b.const_int(b.ctx.i32_ty, 3);
461 let c10 = b.const_int(b.ctx.i32_ty, 10);
462 let sum = b.build_add("sum", c2, c3);
463 let prod = b.build_mul("prod", sum, c10);
464 b.build_ret(prod);
465
466 let mut pass = ConstProp;
467 pass.run_on_function(&mut ctx, &mut module.functions[0]);
468
469 let func = &module.functions[0];
470 assert_eq!(
471 func.blocks[0].body.len(),
472 0,
473 "both sum and prod should be folded"
474 );
475 let tid = func.blocks[0].terminator.unwrap();
476 if let llvm_ir::InstrKind::Ret {
477 val: Some(ValueRef::Constant(cid)),
478 } = &func.instr(tid).kind
479 {
480 assert_eq!(
481 ctx.get_const(*cid),
482 &llvm_ir::ConstantData::Int {
483 ty: ctx.i32_ty,
484 val: 50
485 }
486 );
487 } else {
488 panic!("expected ret with constant 50");
489 }
490 }
491
492 #[test]
493 fn const_prop_across_blocks_rpo() {
494 let mut ctx = Context::new();
500 let mut module = Module::new("test");
501 let mut b = Builder::new(&mut ctx, &mut module);
502 b.add_function("f", b.ctx.i32_ty, vec![], vec![], false, Linkage::External);
503
504 let entry = b.add_block("entry");
505 let exit = b.add_block("exit");
506
507 b.position_at_end(entry);
508 let c2 = b.const_int(b.ctx.i32_ty, 2);
509 let c3 = b.const_int(b.ctx.i32_ty, 3);
510 let a = b.build_add("a", c2, c3);
511 b.build_br(exit);
512
513 b.position_at_end(exit);
514 let c10 = b.const_int(b.ctx.i32_ty, 10);
515 let bv = b.build_add("b", a, c10);
516 b.build_ret(bv);
517
518 let mut pass = ConstProp;
519 pass.run_on_function(&mut ctx, &mut module.functions[0]);
520
521 let func = &module.functions[0];
522 assert_eq!(
524 func.blocks[0].body.len(),
525 0,
526 "`a` should be folded in entry"
527 );
528 assert_eq!(func.blocks[1].body.len(), 0, "`b` should be folded in exit");
529 let tid = func.blocks[1].terminator.unwrap();
531 if let llvm_ir::InstrKind::Ret {
532 val: Some(ValueRef::Constant(cid)),
533 } = &func.instr(tid).kind
534 {
535 assert_eq!(
536 ctx.get_const(*cid),
537 &llvm_ir::ConstantData::Int {
538 ty: ctx.i32_ty,
539 val: 15
540 }
541 );
542 } else {
543 panic!("expected ret with constant 15");
544 }
545 }
546
547 #[test]
548 fn rpo_order_entry_before_successor() {
549 let mut ctx = Context::new();
551 let mut module = Module::new("test");
552 let mut b = Builder::new(&mut ctx, &mut module);
553 b.add_function("f", b.ctx.i32_ty, vec![], vec![], false, Linkage::External);
554 let entry = b.add_block("entry");
555 let exit = b.add_block("exit");
556 b.position_at_end(entry);
557 b.build_br(exit);
558 b.position_at_end(exit);
559 let c = b.const_int(b.ctx.i32_ty, 0);
560 b.build_ret(c);
561
562 let func = &module.functions[0];
563 let order = rpo(func);
564 let entry_pos = order.iter().position(|&i| i == entry.0 as usize).unwrap();
565 let exit_pos = order.iter().position(|&i| i == exit.0 as usize).unwrap();
566 assert!(entry_pos < exit_pos, "entry must come before exit in RPO");
567 }
568}