1use crate::const_prop::subst_kind;
19use crate::pass::FunctionPass;
20use llvm_analysis::{Cfg, DomTree};
21use llvm_ir::{BlockId, Context, Function, InstrId, InstrKind, Instruction, TypeId, ValueRef};
22use std::collections::{HashMap, HashSet, VecDeque};
23
24pub struct Mem2Reg;
40
41impl FunctionPass for Mem2Reg {
42 fn name(&self) -> &'static str {
43 "mem2reg"
44 }
45
46 fn run_on_function(&mut self, ctx: &mut Context, func: &mut Function) -> bool {
47 if func.blocks.is_empty() {
48 return false;
49 }
50
51 let promotable = find_promotable_allocas(func);
52 if promotable.is_empty() {
53 return false;
54 }
55
56 let cfg = Cfg::compute(func);
57 let dom = DomTree::compute(func, &cfg);
58 let df = dom.dominance_frontier(&cfg);
59
60 let phi_map = insert_phis(ctx, func, &promotable, &df, &cfg);
62
63 let mut subst: HashMap<InstrId, ValueRef> = HashMap::new();
65 let mut instrs_to_remove: HashSet<InstrId> = HashSet::new();
66 let mut phi_updates: Vec<(InstrId, BlockId, ValueRef)> = Vec::new();
67
68 let mut stacks: HashMap<InstrId, Vec<ValueRef>> = promotable
70 .iter()
71 .map(|(&iid, &ty)| (iid, vec![ValueRef::Constant(ctx.const_undef(ty))]))
72 .collect();
73
74 let n = func.num_blocks();
76 let mut dom_children: Vec<Vec<BlockId>> = vec![Vec::new(); n];
77 for bi in 0..n {
78 let bid = BlockId(bi as u32);
79 if let Some(idom) = dom.idom(bid) {
80 dom_children[idom.0 as usize].push(bid);
81 }
82 }
83
84 rename_dfs(
85 BlockId(0),
86 func,
87 &promotable,
88 &phi_map,
89 &cfg,
90 &dom_children,
91 &mut stacks,
92 &mut subst,
93 &mut instrs_to_remove,
94 &mut phi_updates,
95 );
96
97 for (phi_iid, pred, new_val) in phi_updates {
99 if let InstrKind::Phi {
100 ref mut incoming, ..
101 } = func.instr_mut(phi_iid).kind
102 {
103 for (val, blk) in incoming.iter_mut() {
104 if *blk == pred {
105 *val = new_val;
106 break;
107 }
108 }
109 }
110 }
111
112 if !subst.is_empty() {
114 for instr in func.instructions.iter_mut() {
115 let new_kind = subst_kind(instr.kind.clone(), &subst);
116 instr.kind = new_kind;
117 }
118 }
119
120 instrs_to_remove.extend(promotable.keys().copied());
122 for bb in &mut func.blocks {
123 bb.body.retain(|id| !instrs_to_remove.contains(id));
124 }
125
126 true
127 }
128}
129
130fn find_promotable_allocas(func: &Function) -> HashMap<InstrId, TypeId> {
146 let mut result = HashMap::new();
147 let entry = match func.blocks.first() {
148 Some(b) => b,
149 None => return result,
150 };
151
152 let alloca_iids: Vec<InstrId> = entry
153 .body
154 .iter()
155 .copied()
156 .filter(|&iid| {
157 matches!(
158 func.instr(iid).kind,
159 InstrKind::Alloca {
160 num_elements: None,
161 ..
162 }
163 )
164 })
165 .collect();
166
167 'outer: for &alloca_iid in &alloca_iids {
168 let alloc_ty = match func.instr(alloca_iid).kind {
169 InstrKind::Alloca { alloc_ty, .. } => alloc_ty,
170 _ => unreachable!(),
171 };
172 let ptr = ValueRef::Instruction(alloca_iid);
173
174 for instr in &func.instructions {
175 match &instr.kind {
176 InstrKind::Load {
178 ptr: p,
179 volatile: false,
180 ..
181 } if *p == ptr => {}
182 InstrKind::Store {
184 ptr: p,
185 val,
186 volatile: false,
187 ..
188 } if *p == ptr => {
189 if *val == ptr {
191 continue 'outer;
192 }
193 }
194 kind if kind.operands().contains(&ptr) => continue 'outer,
196 _ => {}
197 }
198 }
199
200 result.insert(alloca_iid, alloc_ty);
201 }
202
203 result
204}
205
206fn insert_phis(
218 ctx: &mut Context,
219 func: &mut Function,
220 promotable: &HashMap<InstrId, TypeId>,
221 df: &HashMap<BlockId, Vec<BlockId>>,
222 cfg: &Cfg,
223) -> HashMap<BlockId, Vec<(InstrId, InstrId)>> {
224 let mut phi_map: HashMap<BlockId, Vec<(InstrId, InstrId)>> = HashMap::new();
225
226 for (&alloca_iid, &alloc_ty) in promotable {
227 let def_blocks = find_def_blocks(func, alloca_iid);
228 let idf = iterated_df(&def_blocks, df);
229
230 for block_id in idf {
231 let preds = cfg.predecessors(block_id);
232 let undef = ctx.const_undef(alloc_ty);
233 let incoming: Vec<(ValueRef, BlockId)> = preds
234 .iter()
235 .map(|&p| (ValueRef::Constant(undef), p))
236 .collect();
237
238 let phi_name = func.fresh_name();
239 let phi_iid = func.alloc_instr(Instruction {
240 name: Some(phi_name),
241 ty: alloc_ty,
242 kind: InstrKind::Phi {
243 ty: alloc_ty,
244 incoming,
245 },
246 });
247 func.blocks[block_id.0 as usize].body.insert(0, phi_iid);
249 phi_map
250 .entry(block_id)
251 .or_default()
252 .push((alloca_iid, phi_iid));
253 }
254 }
255
256 phi_map
257}
258
259fn find_def_blocks(func: &Function, alloca_iid: InstrId) -> Vec<BlockId> {
260 let ptr = ValueRef::Instruction(alloca_iid);
261 let mut result = Vec::new();
262 for (bi, bb) in func.blocks.iter().enumerate() {
263 for iid in bb.instrs() {
264 if let InstrKind::Store { ptr: p, .. } = &func.instr(iid).kind {
265 if *p == ptr {
266 result.push(BlockId(bi as u32));
267 break;
268 }
269 }
270 }
271 }
272 result
273}
274
275fn iterated_df(def_blocks: &[BlockId], df: &HashMap<BlockId, Vec<BlockId>>) -> Vec<BlockId> {
276 let mut in_idf: HashSet<BlockId> = HashSet::new();
277 let mut worklist: VecDeque<BlockId> = def_blocks.iter().copied().collect();
278 while let Some(b) = worklist.pop_front() {
279 for &y in df.get(&b).map(|v| v.as_slice()).unwrap_or(&[]) {
280 if in_idf.insert(y) {
281 worklist.push_back(y);
282 }
283 }
284 }
285 in_idf.into_iter().collect()
286}
287
288#[allow(clippy::too_many_arguments)]
306fn rename_dfs(
307 block: BlockId,
308 func: &mut Function,
309 promotable: &HashMap<InstrId, TypeId>,
310 phi_map: &HashMap<BlockId, Vec<(InstrId, InstrId)>>,
311 cfg: &Cfg,
312 dom_children: &[Vec<BlockId>],
313 stacks: &mut HashMap<InstrId, Vec<ValueRef>>,
314 subst: &mut HashMap<InstrId, ValueRef>,
315 instrs_to_remove: &mut HashSet<InstrId>,
316 phi_updates: &mut Vec<(InstrId, BlockId, ValueRef)>,
317) {
318 let mut saved: Vec<(InstrId, usize)> = Vec::new();
320
321 if let Some(phis) = phi_map.get(&block) {
323 for &(alloca_iid, phi_iid) in phis {
324 let stack = stacks.get_mut(&alloca_iid).unwrap();
325 saved.push((alloca_iid, stack.len()));
326 stack.push(ValueRef::Instruction(phi_iid));
327 }
328 }
329
330 let body: Vec<InstrId> = func.blocks[block.0 as usize].body.clone();
332 for iid in body {
333 match func.instr(iid).kind.clone() {
334 InstrKind::Alloca { .. } if promotable.contains_key(&iid) => {
335 }
337 InstrKind::Load {
338 ptr: ValueRef::Instruction(alloca_iid),
339 volatile: false,
340 ..
341 } => {
342 if let Some(stack) = stacks.get(&alloca_iid) {
343 let def = *stack.last().unwrap();
344 subst.insert(iid, def);
345 instrs_to_remove.insert(iid);
346 }
347 }
348 InstrKind::Store {
349 val,
350 ptr: ValueRef::Instruction(alloca_iid),
351 volatile: false,
352 ..
353 } => {
354 if stacks.contains_key(&alloca_iid) {
355 let resolved = if let ValueRef::Instruction(vid) = val {
357 subst.get(&vid).copied().unwrap_or(val)
358 } else {
359 val
360 };
361 let stack = stacks.get_mut(&alloca_iid).unwrap();
362 saved.push((alloca_iid, stack.len()));
363 stack.push(resolved);
364 instrs_to_remove.insert(iid);
365 }
366 }
367 _ => {}
368 }
369 }
370
371 for &succ in cfg.successors(block) {
373 if let Some(phis) = phi_map.get(&succ) {
374 for &(alloca_iid, phi_iid) in phis {
375 let def = *stacks[&alloca_iid].last().unwrap();
376 phi_updates.push((phi_iid, block, def));
377 }
378 }
379 }
380
381 let children = dom_children[block.0 as usize].clone();
383 for child in children {
384 rename_dfs(
385 child,
386 func,
387 promotable,
388 phi_map,
389 cfg,
390 dom_children,
391 stacks,
392 subst,
393 instrs_to_remove,
394 phi_updates,
395 );
396 }
397
398 for (alloca_iid, saved_len) in saved.into_iter().rev() {
400 stacks.get_mut(&alloca_iid).unwrap().truncate(saved_len);
401 }
402}
403
404#[cfg(test)]
409mod tests {
410 use super::*;
411 use crate::pass::FunctionPass;
412 use llvm_ir::{Builder, Context, Linkage, Module, ValueRef};
413
414 fn make_simple_fn() -> (Context, Module) {
422 let mut ctx = Context::new();
423 let mut module = Module::new("test");
424 let mut b = Builder::new(&mut ctx, &mut module);
425 b.add_function(
426 "f",
427 b.ctx.i32_ty,
428 vec![b.ctx.i32_ty],
429 vec!["x".into()],
430 false,
431 Linkage::External,
432 );
433 let entry = b.add_block("entry");
434 b.position_at_end(entry);
435 let x = b.get_arg(0);
436 let p = b.build_alloca("p", b.ctx.i32_ty);
437 b.build_store(x, p);
438 let v = b.build_load("v", b.ctx.i32_ty, p);
439 b.build_ret(v);
440 (ctx, module)
441 }
442
443 #[test]
444 fn mem2reg_simple_store_load() {
445 let (mut ctx, mut module) = make_simple_fn();
446
447 assert_eq!(module.functions[0].blocks[0].body.len(), 3);
449
450 let mut pass = Mem2Reg;
451 let changed = pass.run_on_function(&mut ctx, &mut module.functions[0]);
452 assert!(changed);
453
454 let func = &module.functions[0];
456 assert_eq!(
457 func.blocks[0].body.len(),
458 0,
459 "alloca, store, and load should all be removed"
460 );
461
462 let tid = func.blocks[0].terminator.unwrap();
464 if let InstrKind::Ret { val: Some(v) } = &func.instr(tid).kind {
465 assert_eq!(
466 *v,
467 ValueRef::Argument(llvm_ir::ArgId(0)),
468 "ret should use arg %x directly after mem2reg"
469 );
470 } else {
471 panic!("terminator should be ret with a value");
472 }
473 }
474
475 fn make_phi_fn() -> (Context, Module) {
484 let mut ctx = Context::new();
485 let mut module = Module::new("test");
486 let mut b = Builder::new(&mut ctx, &mut module);
487 b.add_function(
488 "f",
489 b.ctx.i32_ty,
490 vec![b.ctx.i1_ty],
491 vec!["cond".into()],
492 false,
493 Linkage::External,
494 );
495 let entry = b.add_block("entry");
496 let then_b = b.add_block("then");
497 let else_b = b.add_block("else");
498 let merge = b.add_block("merge");
499
500 b.position_at_end(entry);
501 let cond = b.get_arg(0);
502 let p = b.build_alloca("p", b.ctx.i32_ty);
503 b.build_cond_br(cond, then_b, else_b);
504
505 b.position_at_end(then_b);
506 let c1 = b.const_int(b.ctx.i32_ty, 1);
507 b.build_store(c1, p);
508 b.build_br(merge);
509
510 b.position_at_end(else_b);
511 let c2 = b.const_int(b.ctx.i32_ty, 2);
512 b.build_store(c2, p);
513 b.build_br(merge);
514
515 b.position_at_end(merge);
516 let v = b.build_load("v", b.ctx.i32_ty, p);
517 b.build_ret(v);
518
519 (ctx, module)
520 }
521
522 #[test]
523 fn mem2reg_inserts_phi() {
524 let (mut ctx, mut module) = make_phi_fn();
525 let mut pass = Mem2Reg;
526 let changed = pass.run_on_function(&mut ctx, &mut module.functions[0]);
527 assert!(changed);
528
529 let func = &module.functions[0];
530 let merge_body = &func.blocks[3].body;
532 assert!(!merge_body.is_empty(), "merge block should have a phi");
533 assert!(
534 matches!(func.instr(merge_body[0]).kind, InstrKind::Phi { .. }),
535 "first instruction in merge should be a phi"
536 );
537 let tid = func.blocks[3].terminator.unwrap();
539 if let InstrKind::Ret {
540 val: Some(ValueRef::Instruction(phi_iid)),
541 } = &func.instr(tid).kind
542 {
543 assert!(
544 matches!(func.instr(*phi_iid).kind, InstrKind::Phi { .. }),
545 "ret should reference the inserted phi"
546 );
547 } else {
548 panic!("ret should use the phi result");
549 }
550 }
551
552 #[test]
553 fn non_promotable_alloca_unchanged() {
554 let mut ctx = Context::new();
556 let mut module = Module::new("test");
557 let mut b = Builder::new(&mut ctx, &mut module);
558 let ptr_ty = b.ctx.ptr_ty;
560 let void_ty = b.ctx.void_ty;
561 let callee_ty = b.ctx.mk_fn_type(void_ty, vec![ptr_ty], false);
562 b.add_function("f", b.ctx.i32_ty, vec![], vec![], false, Linkage::External);
563 let entry = b.add_block("entry");
564 b.position_at_end(entry);
565 let p = b.build_alloca("p", b.ctx.i32_ty);
566 b.build_call(
568 "",
569 b.ctx.void_ty,
570 callee_ty,
571 ValueRef::Global(llvm_ir::GlobalId(0)),
572 vec![p],
573 );
574 let c0 = b.const_int(b.ctx.i32_ty, 0);
575 b.build_ret(c0);
576
577 let before = module.functions[0].blocks[0].body.len();
578 let mut pass = Mem2Reg;
579 let changed = pass.run_on_function(&mut ctx, &mut module.functions[0]);
580 assert!(!changed, "non-promotable alloca must not be removed");
581 assert_eq!(module.functions[0].blocks[0].body.len(), before);
582 }
583}