Skip to main content

llvm_transforms/
inline_pass.rs

1//! Function inlining pass.
2//!
3//! Replaces `call` instructions to small, non-recursive, non-variadic
4//! functions with a copy of the callee body.
5//!
6//! # Algorithm
7//!
8//! For each eligible call site in a caller function:
9//!
10//! 1. **Split** the caller block at the call instruction, producing a
11//!    *pre-block* (instructions before the call) and a *post-block*
12//!    (instructions after the call, plus the original terminator).
13//! 2. **Clone** the callee's blocks into the caller, mapping:
14//!    - Callee `Argument(ArgId(i))` → the i-th call argument.
15//!    - Callee `InstrId(j)` → `InstrId(caller_instr_count + j)`.
16//!    - Callee `BlockId(k)` → `BlockId(caller_block_count + k)`.
17//! 3. **Wire** the pre-block to the cloned callee entry with an unconditional
18//!    branch.
19//! 4. **Replace** each `ret %v` in the clone with `br post-block`, and
20//!    collect return values into a phi at the head of the post-block (or
21//!    directly if there is only one return site).
22//! 5. **Remove** the original call instruction.
23//!
24//! # Eligibility
25//!
26//! A call site is inlineable when:
27//! - The callee is a definition (not a declaration).
28//! - The callee is not variadic.
29//! - The call is not recursive (callee ≠ caller by name).
30//! - The callee body has at most `size_limit` non-terminator instructions.
31
32use 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
41/// Function inlining pass.
42///
43/// Set `size_limit` to control the maximum callee body size (number of
44/// non-terminator instructions) that will be inlined.  The default is 50.
45pub struct Inliner {
46    /// Public API for `size_limit`.
47    pub size_limit: usize,
48    /// Maximum number of inlining rounds per module run.
49    pub max_inline_depth: usize,
50    /// Extra inline budget for callsites inside loop blocks.
51    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        // Inline one call site at a time to keep indices stable.
73        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
86// ---------------------------------------------------------------------------
87// Site selection
88// ---------------------------------------------------------------------------
89
90struct CallSite {
91    caller_id: FunctionId,
92    block_idx: usize, // index into caller.blocks
93    instr_pos: usize, // position in block.body
94    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                    // Callee must be a direct call via GlobalId.  In this IR,
119                    // direct function calls use ValueRef::Global(GlobalId(i))
120                    // where i is the function's index in module.functions.
121                    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                    // Eligibility checks.
134                    if callee_fn.is_declaration {
135                        continue;
136                    }
137                    // Skip variadic callees.
138                    if let llvm_ir::TypeData::Function(ft) = ctx.get_type(*callee_ty) {
139                        if ft.variadic {
140                            continue;
141                        }
142                    }
143                    // Size limit.
144                    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
166// ---------------------------------------------------------------------------
167// Inlining
168// ---------------------------------------------------------------------------
169
170fn 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    // Extract call arguments and result type before borrowing mutably.
179    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    // Compute offsets *before* mutably borrowing the caller.
192    // All cloned InstrIds will be in [instr_offset, instr_offset + clone_size).
193    // All cloned BlockIds will be in [block_offset, block_offset + callee_blocks).
194    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    // Clone callee blocks/instructions into the caller using correct offsets.
200    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    // Step 1: split the caller block at the call site.
212    // pre_block keeps instructions 0..instr_pos (not including the call).
213    // post_block gets instructions instr_pos+1..end plus the original terminator.
214    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    // Truncate original block to pre-call body; remove terminator.
219    caller.blocks[block_idx].body.truncate(instr_pos);
220    caller.blocks[block_idx].terminator = None;
221
222    // Step 2: append cloned blocks into caller.
223    // callee_entry_bid = block_offset (the first cloned block).
224    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    // post_bid is the block added after all cloned callee blocks.
235    let post_bid_actual = BlockId(caller.blocks.len() as u32);
236
237    // Step 3: add post-block.
238    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    // Step 4: wire pre-block → callee entry.
244    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    // Step 5: replace each callee `ret` with `br post_block`.
254    // Also collect return values for phi insertion.
255    let mut return_values: Vec<(BlockId, ValueRef)> = Vec::new();
256    for (callee_blk_id, ret_val) in &callee_ret_sites {
257        // Replace the terminator with br post_bid_actual.
258        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    // Step 6: if the call had a result, wire it to a phi or direct value.
272    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            // Multiple return sites: insert phi at post-block head.
277            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        // Replace all uses of the call result with result_val across ALL blocks.
295        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
311// ---------------------------------------------------------------------------
312// Callee cloning
313// ---------------------------------------------------------------------------
314
315struct ClonedCallee {
316    /// Cloned BasicBlocks (in callee order).
317    /// Block at index i gets caller BlockId `block_offset + i`.
318    blocks: Vec<BasicBlock>,
319    /// Cloned Instructions to append to caller.instructions.
320    instrs: Vec<Instruction>,
321    /// (actual caller BlockId, Option<return_value_in_caller>)
322    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    // instr_map: callee InstrId → actual caller InstrId (= instr_offset + local_idx)
336    // block_map: callee BlockId → actual caller BlockId (= block_offset + bi)
337    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    // Pass 1: allocate new blocks and record block mapping.
345    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    // Pass 2: assign caller InstrIds (instr_offset + sequential index).
352    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    // Pass 3: build cloned instructions with remapped operands and block refs.
365    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                    // Don't clone the ret; record it as a return site.
384                    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                    // Leave new_blocks[bi].terminator = None;
388                    // do_inline will replace it with br post_block.
389                    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// ---------------------------------------------------------------------------
674// Tests
675// ---------------------------------------------------------------------------
676
677#[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    // Build:
684    //   define i32 @add(i32 %a, i32 %b) { ret (a + b) }
685    //   define i32 @caller(i32 %x, i32 %y) { %r = call @add(%x, %y); ret %r }
686    //
687    // @add is FunctionId(0) / GlobalId(0); caller uses ValueRef::Global(GlobalId(0)).
688    fn make_add_module() -> (Context, Module) {
689        let mut ctx = Context::new();
690        let mut module = Module::new("test");
691
692        // Define @add.
693        {
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        // Look up @add's type before borrowing module mutably.
712        let add_fid = module.get_function_id("add").unwrap();
713        let add_callee_ty = module.functions[add_fid.0 as usize].ty;
714
715        // Define @caller.
716        {
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            // @add is at index 0 → ValueRef::Global(GlobalId(0)) references FunctionId(0).
732            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        // A single function with no call instructions — inliner must not inline anything.
759        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        // After inlining @add into @caller:
777        // - @caller should have more blocks than before (entry + cloned callee blocks + post).
778        // - The call instruction should be gone from @caller.
779        let (mut ctx, mut module) = make_add_module();
780
781        // Before: @caller has 1 block, 2 instrs in body (call + nothing; ret is terminator).
782        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        // After inlining a 1-block callee, @caller has: pre-block + 1 callee block + post-block = 3.
791        assert_eq!(
792            caller.blocks.len(),
793            3,
794            "expected pre + callee_entry + post = 3 blocks after inlining"
795        );
796
797        // The pre-block (block 0) should end with a Br to the callee entry.
798        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        // No Call instructions should remain in the caller body.
805        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        // Inliner with size_limit=0 should not inline @add (which has 1 body instruction).
819        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}