1use crate::const_prop::subst_kind;
33use crate::pass::ModulePass;
34use llvm_analysis::{Cfg, DomTree, LoopInfo};
35use llvm_ir::{
36 ArgId, BasicBlock, BlockId, Context, FunctionId, InstrId, InstrKind, Instruction, Module,
37 ValueRef,
38};
39use std::collections::HashMap;
40
41pub struct Inliner {
46 pub size_limit: usize,
48 pub max_inline_depth: usize,
50 pub hot_loop_bonus: usize,
52}
53
54impl Default for Inliner {
55 fn default() -> Self {
56 Inliner {
57 size_limit: 50,
58 max_inline_depth: 8,
59 hot_loop_bonus: 50,
60 }
61 }
62}
63
64impl ModulePass for Inliner {
65 fn name(&self) -> &'static str {
66 "inline"
67 }
68
69 fn run_on_module(&mut self, ctx: &mut Context, module: &mut Module) -> bool {
70 let mut changed = false;
71 let mut depth = 0usize;
72 while depth < self.max_inline_depth {
74 let Some(site) = find_inline_site(ctx, module, self.size_limit, self.hot_loop_bonus)
75 else {
76 break;
77 };
78 do_inline(ctx, module, site);
79 changed = true;
80 depth += 1;
81 }
82 changed
83 }
84}
85
86struct CallSite {
91 caller_id: FunctionId,
92 block_idx: usize, instr_pos: usize, callee_id: FunctionId,
95}
96
97fn find_inline_site(
98 ctx: &Context,
99 module: &Module,
100 size_limit: usize,
101 hot_loop_bonus: usize,
102) -> Option<CallSite> {
103 for (caller_idx, caller) in module.functions.iter().enumerate() {
104 if caller.is_declaration {
105 continue;
106 }
107 let caller_id = FunctionId(caller_idx as u32);
108 let cfg = Cfg::compute(caller);
109 let dom = DomTree::compute(caller, &cfg);
110 let loops = LoopInfo::compute(caller, &cfg, &dom);
111
112 for (bi, bb) in caller.blocks.iter().enumerate() {
113 for (pos, &iid) in bb.body.iter().enumerate() {
114 if let InstrKind::Call {
115 callee, callee_ty, ..
116 } = &caller.instr(iid).kind
117 {
118 let callee_fid = match callee {
122 ValueRef::Global(gid) => {
123 let fid = FunctionId(gid.0);
124 if fid.0 as usize >= module.functions.len() {
125 continue;
126 }
127 fid
128 }
129 _ => continue,
130 };
131 let callee_fn = &module.functions[callee_fid.0 as usize];
132
133 if callee_fn.is_declaration {
135 continue;
136 }
137 if let llvm_ir::TypeData::Function(ft) = ctx.get_type(*callee_ty) {
139 if ft.variadic {
140 continue;
141 }
142 }
143 let body_instrs: usize = callee_fn.blocks.iter().map(|b| b.body.len()).sum();
145 let mut effective_size_limit = size_limit;
146 if loops.depth(BlockId(bi as u32)) > 0 {
147 effective_size_limit = effective_size_limit.saturating_add(hot_loop_bonus);
148 }
149 if body_instrs > effective_size_limit {
150 continue;
151 }
152
153 return Some(CallSite {
154 caller_id,
155 block_idx: bi,
156 instr_pos: pos,
157 callee_id: callee_fid,
158 });
159 }
160 }
161 }
162 }
163 None
164}
165
166fn do_inline(ctx: &mut Context, module: &mut Module, site: CallSite) {
171 let CallSite {
172 caller_id,
173 block_idx,
174 instr_pos,
175 callee_id,
176 } = site;
177
178 let (call_args, call_result_ty, call_iid) = {
180 let caller = &module.functions[caller_id.0 as usize];
181 let bb = &caller.blocks[block_idx];
182 let iid = bb.body[instr_pos];
183 let (args, ty) = if let InstrKind::Call { args, .. } = &caller.instr(iid).kind {
184 (args.clone(), caller.instr(iid).ty)
185 } else {
186 unreachable!()
187 };
188 (args, ty, iid)
189 };
190
191 let (instr_offset, block_offset) = {
195 let caller = &module.functions[caller_id.0 as usize];
196 (caller.instructions.len() as u32, caller.blocks.len() as u32)
197 };
198
199 let callee_clone = clone_callee(
201 ctx,
202 module,
203 callee_id,
204 &call_args,
205 instr_offset,
206 block_offset,
207 );
208
209 let caller = &mut module.functions[caller_id.0 as usize];
210
211 let orig_block = &caller.blocks[block_idx];
215 let post_body: Vec<InstrId> = orig_block.body[instr_pos + 1..].to_vec();
216 let orig_term = orig_block.terminator;
217
218 caller.blocks[block_idx].body.truncate(instr_pos);
220 caller.blocks[block_idx].terminator = None;
221
222 let callee_entry_bid = BlockId(block_offset);
225 let callee_ret_sites = callee_clone.return_sites.clone();
226
227 for bb in callee_clone.blocks {
228 caller.blocks.push(bb);
229 }
230 for instr in callee_clone.instrs {
231 caller.instructions.push(instr);
232 }
233
234 let post_bid_actual = BlockId(caller.blocks.len() as u32);
236
237 let mut post_bb = BasicBlock::new(caller.fresh_name());
239 post_bb.body = post_body;
240 post_bb.terminator = orig_term;
241 caller.blocks.push(post_bb);
242
243 let br_to_callee = caller.alloc_instr(Instruction {
245 name: None,
246 ty: ctx.void_ty,
247 kind: InstrKind::Br {
248 dest: callee_entry_bid,
249 },
250 });
251 caller.blocks[block_idx].set_terminator(br_to_callee);
252
253 let mut return_values: Vec<(BlockId, ValueRef)> = Vec::new();
256 for (callee_blk_id, ret_val) in &callee_ret_sites {
257 let br_iid = caller.alloc_instr(Instruction {
259 name: None,
260 ty: ctx.void_ty,
261 kind: InstrKind::Br {
262 dest: post_bid_actual,
263 },
264 });
265 caller.blocks[callee_blk_id.0 as usize].terminator = Some(br_iid);
266 if let Some(rv) = ret_val {
267 return_values.push((*callee_blk_id, *rv));
268 }
269 }
270
271 if call_result_ty != ctx.void_ty && !return_values.is_empty() {
273 let result_val = if return_values.len() == 1 {
274 return_values[0].1
275 } else {
276 let incoming: Vec<(ValueRef, BlockId)> =
278 return_values.iter().map(|&(b, v)| (v, b)).collect();
279 let phi_name = caller.fresh_name();
280 let phi_iid = caller.alloc_instr(Instruction {
281 name: Some(phi_name),
282 ty: call_result_ty,
283 kind: InstrKind::Phi {
284 ty: call_result_ty,
285 incoming,
286 },
287 });
288 caller.blocks[post_bid_actual.0 as usize]
289 .body
290 .insert(0, phi_iid);
291 ValueRef::Instruction(phi_iid)
292 };
293
294 let subst: HashMap<InstrId, ValueRef> = [(call_iid, result_val)].into();
296 let num_blocks = caller.blocks.len();
297 for bi in 0..num_blocks {
298 let body_iids: Vec<InstrId> = caller.blocks[bi].body.clone();
299 for iid in body_iids {
300 let new_kind = subst_kind(caller.instr(iid).kind.clone(), &subst);
301 caller.instr_mut(iid).kind = new_kind;
302 }
303 if let Some(tid) = caller.blocks[bi].terminator {
304 let new_kind = subst_kind(caller.instr(tid).kind.clone(), &subst);
305 caller.instr_mut(tid).kind = new_kind;
306 }
307 }
308 }
309}
310
311struct ClonedCallee {
316 blocks: Vec<BasicBlock>,
319 instrs: Vec<Instruction>,
321 return_sites: Vec<(BlockId, Option<ValueRef>)>,
323}
324
325fn clone_callee(
326 _ctx: &mut Context,
327 module: &Module,
328 callee_id: FunctionId,
329 call_args: &[ValueRef],
330 instr_offset: u32,
331 block_offset: u32,
332) -> ClonedCallee {
333 let callee = &module.functions[callee_id.0 as usize];
334
335 let mut instr_map: HashMap<InstrId, InstrId> = HashMap::new();
338 let mut block_map: HashMap<BlockId, BlockId> = HashMap::new();
339
340 let mut new_instrs: Vec<Instruction> = Vec::new();
341 let mut new_blocks: Vec<BasicBlock> = Vec::new();
342 let mut return_sites: Vec<(BlockId, Option<ValueRef>)> = Vec::new();
343
344 for (bi, bb) in callee.blocks.iter().enumerate() {
346 let caller_bid = BlockId(block_offset + bi as u32);
347 block_map.insert(BlockId(bi as u32), caller_bid);
348 new_blocks.push(BasicBlock::new(bb.name.clone()));
349 }
350
351 let mut local_idx: u32 = 0;
353 for bb in &callee.blocks {
354 for &iid in &bb.body {
355 instr_map.insert(iid, InstrId(instr_offset + local_idx));
356 local_idx += 1;
357 }
358 if let Some(tid) = bb.terminator {
359 instr_map.insert(tid, InstrId(instr_offset + local_idx));
360 local_idx += 1;
361 }
362 }
363
364 local_idx = 0;
366 for (bi, bb) in callee.blocks.iter().enumerate() {
367 for &iid in &bb.body {
368 let orig = callee.instr(iid);
369 let new_kind = remap_kind(orig.kind.clone(), &instr_map, call_args, &block_map);
370 new_instrs.push(Instruction {
371 name: orig.name.clone(),
372 ty: orig.ty,
373 kind: new_kind,
374 });
375 new_blocks[bi].body.push(InstrId(instr_offset + local_idx));
376 local_idx += 1;
377 }
378
379 if let Some(tid) = bb.terminator {
380 let orig = callee.instr(tid);
381 match &orig.kind {
382 InstrKind::Ret { val } => {
383 let mapped_val = val.map(|v| remap_val(v, &instr_map, call_args));
385 let caller_bid = block_map[&BlockId(bi as u32)];
386 return_sites.push((caller_bid, mapped_val));
387 local_idx += 1;
390 }
391 _ => {
392 let new_kind = remap_kind(orig.kind.clone(), &instr_map, call_args, &block_map);
393 new_instrs.push(Instruction {
394 name: orig.name.clone(),
395 ty: orig.ty,
396 kind: new_kind,
397 });
398 new_blocks[bi].terminator = Some(InstrId(instr_offset + local_idx));
399 local_idx += 1;
400 }
401 }
402 }
403 }
404
405 ClonedCallee {
406 blocks: new_blocks,
407 instrs: new_instrs,
408 return_sites,
409 }
410}
411
412fn remap_val(
413 v: ValueRef,
414 instr_map: &HashMap<InstrId, InstrId>,
415 call_args: &[ValueRef],
416) -> ValueRef {
417 match v {
418 ValueRef::Argument(ArgId(i)) => call_args.get(i as usize).copied().unwrap_or(v),
419 ValueRef::Instruction(iid) => ValueRef::Instruction(*instr_map.get(&iid).unwrap_or(&iid)),
420 other => other,
421 }
422}
423
424fn remap_kind(
425 kind: InstrKind,
426 instr_map: &HashMap<InstrId, InstrId>,
427 call_args: &[ValueRef],
428 block_map: &HashMap<BlockId, BlockId>,
429) -> InstrKind {
430 let s = |v: ValueRef| remap_val(v, instr_map, call_args);
431 let b = |bid: BlockId| *block_map.get(&bid).unwrap_or(&bid);
432
433 match kind {
434 InstrKind::Add { flags, lhs, rhs } => InstrKind::Add {
435 flags,
436 lhs: s(lhs),
437 rhs: s(rhs),
438 },
439 InstrKind::Sub { flags, lhs, rhs } => InstrKind::Sub {
440 flags,
441 lhs: s(lhs),
442 rhs: s(rhs),
443 },
444 InstrKind::Mul { flags, lhs, rhs } => InstrKind::Mul {
445 flags,
446 lhs: s(lhs),
447 rhs: s(rhs),
448 },
449 InstrKind::UDiv { exact, lhs, rhs } => InstrKind::UDiv {
450 exact,
451 lhs: s(lhs),
452 rhs: s(rhs),
453 },
454 InstrKind::SDiv { exact, lhs, rhs } => InstrKind::SDiv {
455 exact,
456 lhs: s(lhs),
457 rhs: s(rhs),
458 },
459 InstrKind::URem { lhs, rhs } => InstrKind::URem {
460 lhs: s(lhs),
461 rhs: s(rhs),
462 },
463 InstrKind::SRem { lhs, rhs } => InstrKind::SRem {
464 lhs: s(lhs),
465 rhs: s(rhs),
466 },
467 InstrKind::And { lhs, rhs } => InstrKind::And {
468 lhs: s(lhs),
469 rhs: s(rhs),
470 },
471 InstrKind::Or { lhs, rhs } => InstrKind::Or {
472 lhs: s(lhs),
473 rhs: s(rhs),
474 },
475 InstrKind::Xor { lhs, rhs } => InstrKind::Xor {
476 lhs: s(lhs),
477 rhs: s(rhs),
478 },
479 InstrKind::Shl { flags, lhs, rhs } => InstrKind::Shl {
480 flags,
481 lhs: s(lhs),
482 rhs: s(rhs),
483 },
484 InstrKind::LShr { exact, lhs, rhs } => InstrKind::LShr {
485 exact,
486 lhs: s(lhs),
487 rhs: s(rhs),
488 },
489 InstrKind::AShr { exact, lhs, rhs } => InstrKind::AShr {
490 exact,
491 lhs: s(lhs),
492 rhs: s(rhs),
493 },
494 InstrKind::FAdd { flags, lhs, rhs } => InstrKind::FAdd {
495 flags,
496 lhs: s(lhs),
497 rhs: s(rhs),
498 },
499 InstrKind::FSub { flags, lhs, rhs } => InstrKind::FSub {
500 flags,
501 lhs: s(lhs),
502 rhs: s(rhs),
503 },
504 InstrKind::FMul { flags, lhs, rhs } => InstrKind::FMul {
505 flags,
506 lhs: s(lhs),
507 rhs: s(rhs),
508 },
509 InstrKind::FDiv { flags, lhs, rhs } => InstrKind::FDiv {
510 flags,
511 lhs: s(lhs),
512 rhs: s(rhs),
513 },
514 InstrKind::FRem { flags, lhs, rhs } => InstrKind::FRem {
515 flags,
516 lhs: s(lhs),
517 rhs: s(rhs),
518 },
519 InstrKind::FNeg { flags, operand } => InstrKind::FNeg {
520 flags,
521 operand: s(operand),
522 },
523 InstrKind::ICmp { pred, lhs, rhs } => InstrKind::ICmp {
524 pred,
525 lhs: s(lhs),
526 rhs: s(rhs),
527 },
528 InstrKind::FCmp {
529 flags,
530 pred,
531 lhs,
532 rhs,
533 } => InstrKind::FCmp {
534 flags,
535 pred,
536 lhs: s(lhs),
537 rhs: s(rhs),
538 },
539 InstrKind::Alloca {
540 alloc_ty,
541 num_elements,
542 align,
543 } => InstrKind::Alloca {
544 alloc_ty,
545 num_elements: num_elements.map(s),
546 align,
547 },
548 InstrKind::Load {
549 ty,
550 ptr,
551 align,
552 volatile,
553 } => InstrKind::Load {
554 ty,
555 ptr: s(ptr),
556 align,
557 volatile,
558 },
559 InstrKind::Store {
560 val,
561 ptr,
562 align,
563 volatile,
564 } => InstrKind::Store {
565 val: s(val),
566 ptr: s(ptr),
567 align,
568 volatile,
569 },
570 InstrKind::GetElementPtr {
571 inbounds,
572 base_ty,
573 ptr,
574 indices,
575 } => InstrKind::GetElementPtr {
576 inbounds,
577 base_ty,
578 ptr: s(ptr),
579 indices: indices.into_iter().map(s).collect(),
580 },
581 InstrKind::Trunc { val, to } => InstrKind::Trunc { val: s(val), to },
582 InstrKind::ZExt { val, to } => InstrKind::ZExt { val: s(val), to },
583 InstrKind::SExt { val, to } => InstrKind::SExt { val: s(val), to },
584 InstrKind::FPTrunc { val, to } => InstrKind::FPTrunc { val: s(val), to },
585 InstrKind::FPExt { val, to } => InstrKind::FPExt { val: s(val), to },
586 InstrKind::FPToUI { val, to } => InstrKind::FPToUI { val: s(val), to },
587 InstrKind::FPToSI { val, to } => InstrKind::FPToSI { val: s(val), to },
588 InstrKind::UIToFP { val, to } => InstrKind::UIToFP { val: s(val), to },
589 InstrKind::SIToFP { val, to } => InstrKind::SIToFP { val: s(val), to },
590 InstrKind::PtrToInt { val, to } => InstrKind::PtrToInt { val: s(val), to },
591 InstrKind::IntToPtr { val, to } => InstrKind::IntToPtr { val: s(val), to },
592 InstrKind::BitCast { val, to } => InstrKind::BitCast { val: s(val), to },
593 InstrKind::AddrSpaceCast { val, to } => InstrKind::AddrSpaceCast { val: s(val), to },
594 InstrKind::Freeze { val } => InstrKind::Freeze { val: s(val) },
595 InstrKind::Select {
596 cond,
597 then_val,
598 else_val,
599 } => InstrKind::Select {
600 cond: s(cond),
601 then_val: s(then_val),
602 else_val: s(else_val),
603 },
604 InstrKind::Phi { ty, incoming } => InstrKind::Phi {
605 ty,
606 incoming: incoming
607 .into_iter()
608 .map(|(v, blk)| (s(v), b(blk)))
609 .collect(),
610 },
611 InstrKind::ExtractValue { aggregate, indices } => InstrKind::ExtractValue {
612 aggregate: s(aggregate),
613 indices,
614 },
615 InstrKind::InsertValue {
616 aggregate,
617 val,
618 indices,
619 } => InstrKind::InsertValue {
620 aggregate: s(aggregate),
621 val: s(val),
622 indices,
623 },
624 InstrKind::ExtractElement { vec, idx } => InstrKind::ExtractElement {
625 vec: s(vec),
626 idx: s(idx),
627 },
628 InstrKind::InsertElement { vec, val, idx } => InstrKind::InsertElement {
629 vec: s(vec),
630 val: s(val),
631 idx: s(idx),
632 },
633 InstrKind::ShuffleVector { v1, v2, mask } => InstrKind::ShuffleVector {
634 v1: s(v1),
635 v2: s(v2),
636 mask,
637 },
638 InstrKind::Call {
639 tail,
640 callee_ty,
641 callee,
642 args,
643 } => InstrKind::Call {
644 tail,
645 callee_ty,
646 callee: s(callee),
647 args: args.into_iter().map(s).collect(),
648 },
649 InstrKind::Ret { val } => InstrKind::Ret { val: val.map(s) },
650 InstrKind::Br { dest } => InstrKind::Br { dest: b(dest) },
651 InstrKind::CondBr {
652 cond,
653 then_dest,
654 else_dest,
655 } => InstrKind::CondBr {
656 cond: s(cond),
657 then_dest: b(then_dest),
658 else_dest: b(else_dest),
659 },
660 InstrKind::Switch {
661 val,
662 default,
663 cases,
664 } => InstrKind::Switch {
665 val: s(val),
666 default: b(default),
667 cases: cases.into_iter().map(|(v, blk)| (s(v), b(blk))).collect(),
668 },
669 InstrKind::Unreachable => InstrKind::Unreachable,
670 }
671}
672
673#[cfg(test)]
678mod tests {
679 use super::*;
680 use crate::pass::ModulePass;
681 use llvm_ir::{Builder, Context, Function, GlobalId, InstrKind, Linkage, Module, ValueRef};
682
683 fn make_add_module() -> (Context, Module) {
689 let mut ctx = Context::new();
690 let mut module = Module::new("test");
691
692 {
694 let mut b = Builder::new(&mut ctx, &mut module);
695 b.add_function(
696 "add",
697 b.ctx.i32_ty,
698 vec![b.ctx.i32_ty, b.ctx.i32_ty],
699 vec!["a".into(), "b".into()],
700 false,
701 Linkage::External,
702 );
703 let entry = b.add_block("entry");
704 b.position_at_end(entry);
705 let a = b.get_arg(0);
706 let bv = b.get_arg(1);
707 let sum = b.build_add("sum", a, bv);
708 b.build_ret(sum);
709 }
710
711 let add_fid = module.get_function_id("add").unwrap();
713 let add_callee_ty = module.functions[add_fid.0 as usize].ty;
714
715 {
717 let mut b = Builder::new(&mut ctx, &mut module);
718 let i32_ty = b.ctx.i32_ty;
719 b.add_function(
720 "caller",
721 i32_ty,
722 vec![i32_ty, i32_ty],
723 vec!["x".into(), "y".into()],
724 false,
725 Linkage::External,
726 );
727 let entry = b.add_block("entry");
728 b.position_at_end(entry);
729 let x = b.get_arg(0);
730 let y = b.get_arg(1);
731 let r = b.build_call(
733 "r",
734 i32_ty,
735 add_callee_ty,
736 ValueRef::Global(GlobalId(0)),
737 vec![x, y],
738 );
739 b.build_ret(r);
740 }
741
742 (ctx, module)
743 }
744
745 #[test]
746 fn inliner_skips_declarations() {
747 let mut ctx = Context::new();
748 let fn_ty = ctx.mk_fn_type(ctx.void_ty, vec![], false);
749 let decl = Function::new_declaration("ext", fn_ty, vec![], Linkage::External);
750 let mut module = Module::new("test");
751 module.add_function(decl);
752 let mut pass = Inliner::default();
753 assert!(!pass.run_on_module(&mut ctx, &mut module));
754 }
755
756 #[test]
757 fn inliner_no_eligible_call() {
758 let mut ctx = Context::new();
760 let mut module = Module::new("test");
761 {
762 let mut b = Builder::new(&mut ctx, &mut module);
763 let i32_ty = b.ctx.i32_ty;
764 b.add_function("f", i32_ty, vec![], vec![], false, Linkage::External);
765 let entry = b.add_block("entry");
766 b.position_at_end(entry);
767 let c0 = b.const_int(i32_ty, 0);
768 b.build_ret(c0);
769 }
770 let mut pass = Inliner::default();
771 assert!(!pass.run_on_module(&mut ctx, &mut module));
772 }
773
774 #[test]
775 fn inliner_inlines_simple_call() {
776 let (mut ctx, mut module) = make_add_module();
780
781 let caller_before_blocks = module.functions[1].blocks.len();
783 assert_eq!(caller_before_blocks, 1);
784
785 let mut pass = Inliner::default();
786 let changed = pass.run_on_module(&mut ctx, &mut module);
787 assert!(changed, "inliner should have inlined @add");
788
789 let caller = &module.functions[1];
790 assert_eq!(
792 caller.blocks.len(),
793 3,
794 "expected pre + callee_entry + post = 3 blocks after inlining"
795 );
796
797 let pre_term = caller.blocks[0].terminator.unwrap();
799 assert!(
800 matches!(caller.instr(pre_term).kind, InstrKind::Br { .. }),
801 "pre-block should end with unconditional Br"
802 );
803
804 let has_call = caller.blocks.iter().any(|bb| {
806 bb.body
807 .iter()
808 .any(|&iid| matches!(caller.instr(iid).kind, InstrKind::Call { .. }))
809 });
810 assert!(
811 !has_call,
812 "call instruction should have been removed after inlining"
813 );
814 }
815
816 #[test]
817 fn inliner_respects_size_limit() {
818 let (mut ctx, mut module) = make_add_module();
820 let mut pass = Inliner {
821 size_limit: 0,
822 ..Default::default()
823 };
824 let changed = pass.run_on_module(&mut ctx, &mut module);
825 assert!(!changed, "should not inline when callee exceeds size limit");
826 }
827
828 #[test]
829 fn inliner_depth_limit_stops_recursive_growth() {
830 let mut ctx = Context::new();
831 let mut module = Module::new("test");
832 let mut b = Builder::new(&mut ctx, &mut module);
833
834 b.add_function(
835 "f",
836 b.ctx.i64_ty,
837 vec![b.ctx.i64_ty],
838 vec!["x".into()],
839 false,
840 Linkage::External,
841 );
842 let entry = b.add_block("entry");
843 b.position_at_end(entry);
844 let x = b.get_arg(0);
845 let fn_ty = b.ctx.mk_fn_type(b.ctx.i64_ty, vec![b.ctx.i64_ty], false);
846 let call = b.build_call(
847 "y",
848 b.ctx.i64_ty,
849 fn_ty,
850 ValueRef::Global(GlobalId(0)),
851 vec![x],
852 );
853 b.build_ret(call);
854
855 let before_instrs = module.functions[0].instructions.len();
856 let mut pass = Inliner {
857 size_limit: 20,
858 max_inline_depth: 1,
859 hot_loop_bonus: 0,
860 };
861 let changed = pass.run_on_module(&mut ctx, &mut module);
862 assert!(changed, "one recursive inline step should be allowed");
863 let after_instrs = module.functions[0].instructions.len();
864 assert!(after_instrs > before_instrs);
865 }
866
867 #[test]
868 fn inliner_hot_loop_bonus_allows_larger_callee() {
869 let mut ctx = Context::new();
870 let mut module = Module::new("test");
871 let mut b = Builder::new(&mut ctx, &mut module);
872
873 b.add_function(
874 "big",
875 b.ctx.i64_ty,
876 vec![b.ctx.i64_ty],
877 vec!["x".into()],
878 false,
879 Linkage::External,
880 );
881 let big_entry = b.add_block("big_entry");
882 b.position_at_end(big_entry);
883 let x = b.get_arg(0);
884 let c1 = b.const_int(b.ctx.i64_ty, 1);
885 let c2 = b.const_int(b.ctx.i64_ty, 2);
886 let c3 = b.const_int(b.ctx.i64_ty, 3);
887 let a = b.build_add("a", x, c1);
888 let c = b.build_add("c", a, c2);
889 let d = b.build_add("d", c, c3);
890 b.build_ret(d);
891
892 b.add_function(
893 "caller",
894 b.ctx.i64_ty,
895 vec![b.ctx.i64_ty, b.ctx.i1_ty],
896 vec!["x".into(), "cond".into()],
897 false,
898 Linkage::External,
899 );
900 let entry = b.add_block("entry");
901 let loop_bb = b.add_block("loop");
902 let exit = b.add_block("exit");
903 b.position_at_end(entry);
904 let xarg = b.get_arg(0);
905 let cond = b.get_arg(1);
906 b.build_br(loop_bb);
907 b.position_at_end(loop_bb);
908 let big_ty = b.ctx.mk_fn_type(b.ctx.i64_ty, vec![b.ctx.i64_ty], false);
909 let _v = b.build_call(
910 "v",
911 b.ctx.i64_ty,
912 big_ty,
913 ValueRef::Global(GlobalId(0)),
914 vec![xarg],
915 );
916 b.build_cond_br(cond, loop_bb, exit);
917 b.position_at_end(exit);
918 b.build_ret(xarg);
919
920 let caller_idx = module
921 .functions
922 .iter()
923 .position(|f| f.name == "caller")
924 .expect("caller exists");
925 let before = module.functions[caller_idx].instructions.len();
926
927 let mut pass = Inliner {
928 size_limit: 1,
929 max_inline_depth: 4,
930 hot_loop_bonus: 8,
931 };
932 let changed = pass.run_on_module(&mut ctx, &mut module);
933 assert!(changed, "hot-loop bonus should allow inlining in loop block");
934 let after = module.functions[caller_idx].instructions.len();
935 assert!(after > before);
936 }
937}