Skip to main content

rucc_opt/
inline.rs

1//! The inliner, for the calls gcc inlines at every level, those to an `always_inline` function,
2//! and from `-O1` up for the calls to a small function declared `inline`.
3//!
4//! Design: `spec/optimizer/33-inlining.md`, and tamnd/rucc#392.
5//!
6//! ```c
7//! extern inline __attribute__((always_inline, gnu_inline)) int
8//! printf (const char *fmt, ...)
9//! {
10//!   return __printf_chk (1, fmt, __builtin_va_arg_pack ());
11//! }
12//! ```
13//!
14//! `always_inline` is not a hint. gcc inlines every direct call to one of these whatever the level,
15//! `-O0` included, and a header that defines one is relying on that in two ways. The first is that
16//! the definition above is an inline definition, so no unit is obliged to have a copy of it out of
17//! line. The second is `__builtin_va_arg_pack`, which stands for the anonymous arguments of the call
18//! the body was inlined into and means nothing anywhere else. glibc's fortified headers are written
19//! this way, and so is `va-arg-pack-1.c` in the torture suite.
20//!
21//! So this runs first, before anything else in the pipeline and at every level, since `objsize`
22//! right behind it wants to see the caller's objects through the wrapper's parameters. Each
23//! function is settled before it is inlined anywhere, so a body goes in with the `always_inline`
24//! calls inside it already gone, and a function that reaches itself again through such calls is
25//! refused rather than unrolled.
26//!
27//! A call is spliced in place. The block it is in is split after it, and the part after becomes a
28//! block that takes the call's results as parameters. The callee's blocks are copied in with every
29//! side table they point into, its entry is jumped to with the arguments, a `return` becomes a jump
30//! to the second half, and an `alloca` of a fixed size goes to the caller's entry block, where the
31//! verifier wants it.
32//!
33//! A `va_arg_pack` in the callee is the last argument of a call, since that is the one place sema
34//! lets it be written, and it is replaced by the anonymous arguments of the call being inlined.
35//! What makes that more than a list splice is the calling convention: the lowering has already
36//! decided which of those arguments go in registers and which go in memory, and it decided for the
37//! outer call. SysV x86-64 puts all of a structure in registers or none of it, so a structure that
38//! travelled as two registers in the outer call and would find only one left in the inner one goes
39//! to memory there instead, stored to a slot in the caller just ahead of the call. The one case
40//! refused is the other way round, a small structure in memory that the inner call would have
41//! room for. A call that is refused stays a call. A `va_arg_pack_len` is replaced by the count.
42//!
43//! What is left is the out of line copy of a function that still holds either of them, which is a
44//! function nothing can emit. It becomes a declaration, which is gcc's answer too: gcc emits nothing
45//! for an inline definition, so a call the inliner left goes to whatever the rest of the program
46//! defines under the name, which for a glibc wrapper is the library function. A function marked
47//! `inline_only` goes the same way whatever it holds, because its body was only ever here to be
48//! inlined: it is an `extern inline` under GNU's reading, and its external definition is somewhere
49//! else in the program.
50//!
51//! A `static` function marked `always_inline` whose every call went in goes the same way, since
52//! nothing is left that could reach its body. gcc does not emit one either, and the intrinsic
53//! headers depend on that: an operand such as `pextrd`'s lane number is an `i` constraint over a
54//! parameter, which is a constant in every copy spliced into a caller and a register in the out of
55//! line one, and no instruction takes its lane number in a register.
56//!
57//! From `-O1` up the same splice takes a call to a function declared `inline` whose body, once its
58//! own calls are settled, is no larger than `max-inline-insns-single`, the limit gcc gives such a
59//! callee. It is the declared half of gcc's early inliner and not the rest of it: a function
60//! nobody declared `inline` is left alone however small it is, and nothing here weighs the call
61//! against the growth the way section 33.4 wants the later inliner to. What it is for is the code
62//! after it. A `__builtin_constant_p` in the body of such a function asks about a parameter, and
63//! only once the body is where the call was can the answer be the constant the caller passed,
64//! which is what gcc answers and what `bcp-1.c` checks. `-fno-inline` turns this half off and
65//! leaves `always_inline` alone, which is what the flag does in gcc.
66//!
67//! From `-O1` up it also takes the call to a `static` function that nothing reaches any other way,
68//! which is the called once rule of section 33.1 and gcc's `-finline-functions-called-once`. The
69//! function need not be declared `inline` and may be as large as `max-inline-functions-called-once
70//! -insns`, because once its one call is inlined nothing refers to it and it becomes a declaration,
71//! which emits nothing, so the program loses a call and gains nothing. That has to happen here: the
72//! lowering chose which `static` functions to emit from the source, before any call was inlined.
73//! A `static` helper called from one loop is the shape
74//! this is for, and it is everywhere in C, which is tamnd/rucc#1932. Reaching it any other way is a
75//! second call site, a tail call, its address taken by an instruction or written into an image, or
76//! an alias naming it. `used`, `noinline`, `optnone` and `naked` each keep it a call. So does a call
77//! more than `max-inline-functions-called-once-loop-depth` loops deep, which is 6, as it does in
78//! gcc. `-fno-inline` turns this off with the declared half, which is what gcc does, and
79//! `-fno-inline-functions-called-once` turns it off alone.
80//!
81//! A body that takes the address of one of its own labels is copied with the label, so each copy
82//! has an address of its own, which is what gcc does and what `990208-1.c` checks. A body that
83//! jumps to such an address, or whose labels a static table holds, is refused, since the copy
84//! would still be reaching into the original.
85
86use std::collections::{HashMap, HashSet};
87
88use rucc_base::Symbol;
89use rucc_cost::heuristics::{INLINE_CALLED_ONCE_INSNS, INLINE_CALLED_ONCE_LOOP_DEPTH};
90use rucc_ir::{
91    Abi, AsmInfo, AttrSet, Block, BlockCall, BlockCallList, CallInfo, Datum, Def, Drains, Extra,
92    Float, Func, FuncId, Imm, Inst, InstData, Linkage, MemInfo, MemOrder, Module, Opcode, Restrict,
93    Signature, SwitchInfo, Type, VaInfo, Value, ValueList,
94};
95use rucc_target::Isa;
96use rucc_tuple::{Arch, Os};
97
98use crate::Stats;
99use crate::cfg::Cfg;
100use crate::dom::Dominators;
101use crate::loops::Loops;
102
103/// What the step calls itself in a remark, and the name `-fno-inline` turns the declared half off
104/// by.
105pub const NAME: &str = "inline";
106
107/// What `-finline-functions-called-once` and its `-fno-` form toggle, which is the called once half
108/// of this step alone. Not the name of a pass.
109pub const ONCE: &str = "inline-functions-called-once";
110
111const INLINED: &str = "always_inline call inlined";
112
113const HINT_INLINED: &str = "inline call inlined";
114
115const ONCE_INLINED: &str = "call to a static function called once inlined";
116
117/// Which of the two reasons a function is inlined for.
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119enum Kind {
120    /// `always_inline`, which is a promise.
121    Always,
122    /// `inline`, which is a hint taken when the body is small enough.
123    Hinted,
124    /// A `static` function reached by one call and no other way, whose out of line copy goes
125    /// away once that call is inlined.
126    Once,
127}
128
129/// Why a call to an `always_inline` function was not inlined.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum InlineFailure {
132    /// The function reaches itself through calls of this kind.
133    Recursive,
134    /// The arguments or the results of the call are not what the body takes and gives.
135    Mismatch,
136    /// A parameter is a structure passed by value, whose copy the call is what makes. Only a call
137    /// that is not `always_inline` is refused for this. An `always_inline` one is inlined with the
138    /// copy made in the caller.
139    ByValue,
140    /// The body starts a variable argument list of its own, which only a frame of its own has.
141    VaStart,
142    /// The body jumps to a label by its address, or a static table holds one of its labels,
143    /// either of which would still name the original body from the copy.
144    ComputedGoto,
145    /// The body calls `setjmp`, whose frame would become the caller's.
146    Setjmp,
147    /// The body saves the registers it was called with, which in the caller hold the caller's.
148    ApplyArgs,
149    /// The IR has memory SSA in it, which this step runs before.
150    MemorySsa,
151    /// A `va_arg_pack` whose arguments cannot be forwarded to where it is.
152    Pack,
153    /// The body grows the stack by an amount only known when it runs, which in a loop in the
154    /// caller would grow it once for every time round. Only a hint is refused for this.
155    Alloca,
156    /// The body is larger than a callee declared `inline` is allowed to be.
157    TooLarge,
158    /// The call is inside more loops than a function called once may be inlined into.
159    TooDeep,
160    /// The call has a landing pad but not in the shape the lowering builds, an `unwound` straight
161    /// after it read by the branch that ends its block, so there is no pad to hand the calls the
162    /// body makes once it is copied in.
163    Unwinds,
164    /// The callee is built for x86-64 extensions the caller is not, so its body may use
165    /// instructions the caller may not assume. gcc's words for it are the ones used.
166    Target,
167}
168
169impl InlineFailure {
170    /// What `-fopt-info` says about it.
171    #[must_use]
172    pub const fn why(self) -> &'static str {
173        match self {
174            Self::Recursive => "always_inline call not inlined: recursive",
175            Self::Mismatch => "always_inline call not inlined: arguments do not match",
176            Self::ByValue => "always_inline call not inlined: structure passed by value",
177            Self::VaStart => "always_inline call not inlined: callee uses va_start",
178            Self::ComputedGoto => "always_inline call not inlined: callee has a computed goto",
179            Self::Setjmp => "always_inline call not inlined: callee calls setjmp",
180            Self::ApplyArgs => "always_inline call not inlined: callee uses __builtin_apply_args",
181            Self::MemorySsa => "always_inline call not inlined: memory SSA present",
182            Self::Pack => "always_inline call not inlined: va_arg_pack cannot be forwarded",
183            Self::Alloca => "always_inline call not inlined: callee calls alloca",
184            Self::TooLarge => "always_inline call not inlined: callee too large",
185            Self::TooDeep => "always_inline call not inlined: call inside too many loops",
186            Self::Unwinds => "always_inline call not inlined: call has a landing pad",
187            Self::Target => "always_inline call not inlined: target specific option mismatch",
188        }
189    }
190
191    /// What `-fopt-info` says about it for a call to a function that was only declared `inline`.
192    #[must_use]
193    pub const fn hint(self) -> &'static str {
194        match self {
195            Self::Recursive => "inline call not inlined: recursive",
196            Self::Mismatch => "inline call not inlined: arguments do not match",
197            Self::ByValue => "inline call not inlined: structure passed by value",
198            Self::VaStart => "inline call not inlined: callee uses va_start",
199            Self::ComputedGoto => "inline call not inlined: callee has a computed goto",
200            Self::Setjmp => "inline call not inlined: callee calls setjmp",
201            Self::ApplyArgs => "inline call not inlined: callee uses __builtin_apply_args",
202            Self::MemorySsa => "inline call not inlined: memory SSA present",
203            Self::Pack => "inline call not inlined: va_arg_pack cannot be forwarded",
204            Self::Alloca => "inline call not inlined: callee calls alloca",
205            Self::TooLarge => "inline call not inlined: callee too large",
206            Self::TooDeep => "inline call not inlined: call inside too many loops",
207            Self::Unwinds => "inline call not inlined: call has a landing pad",
208            Self::Target => "inline call not inlined: target specific option mismatch",
209        }
210    }
211
212    /// What `-fopt-info` says about it for the one call to a `static` function called once.
213    #[must_use]
214    pub const fn once(self) -> &'static str {
215        match self {
216            Self::Recursive => "call to a function called once not inlined: recursive",
217            Self::Mismatch => "call to a function called once not inlined: arguments do not match",
218            Self::ByValue => {
219                "call to a function called once not inlined: structure passed by value"
220            }
221            Self::VaStart => "call to a function called once not inlined: callee uses va_start",
222            Self::ComputedGoto => {
223                "call to a function called once not inlined: callee has a computed goto"
224            }
225            Self::Setjmp => "call to a function called once not inlined: callee calls setjmp",
226            Self::ApplyArgs => {
227                "call to a function called once not inlined: callee uses __builtin_apply_args"
228            }
229            Self::MemorySsa => "call to a function called once not inlined: memory SSA present",
230            Self::Pack => {
231                "call to a function called once not inlined: va_arg_pack cannot be forwarded"
232            }
233            Self::Alloca => "call to a function called once not inlined: callee calls alloca",
234            Self::TooLarge => "call to a function called once not inlined: callee too large",
235            Self::TooDeep => {
236                "call to a function called once not inlined: call inside too many loops"
237            }
238            Self::Unwinds => "call to a function called once not inlined: call has a landing pad",
239            Self::Target => {
240                "call to a function called once not inlined: target specific option mismatch"
241            }
242        }
243    }
244}
245
246/// Inlines every call to an `always_inline` function that can be, and with a `limit` every call to
247/// a function declared `inline` whose body is no larger than that and, when `once` says so, the one
248/// call to a `static` function called once, and says what it did where.
249///
250/// Then turns every function still holding a `va_arg_pack` into a declaration. See the module
251/// documentation for why that is the right thing to do with one.
252///
253/// `isa` is what the module is built for, which is what a function without a `target` attribute
254/// is built for. A callee built for more than its caller is never copied into it.
255pub fn run(module: &mut Module, limit: Option<u32>, once: bool, isa: Isa) -> Vec<(FuncId, Stats)> {
256    let once = if limit.is_some() && once { called_once(module) } else { HashSet::new() };
257    let wanted: HashMap<Symbol, (FuncId, Kind)> = module
258        .funcs()
259        .filter(|&id| !module[id].is_declaration())
260        .filter_map(|id| {
261            let func = &module[id];
262            let set = func.attrs.set;
263            let kind = if set.contains(AttrSet::ALWAYS_INLINE) {
264                Kind::Always
265            } else if limit.is_none()
266                || set.without(AttrSet::NOINLINE | AttrSet::OPTNONE | AttrSet::NAKED) != set
267            {
268                return None;
269            } else if func.linkage == Linkage::Internal
270                && !set.contains(AttrSet::USED)
271                && once.contains(&func.name)
272            {
273                Kind::Once
274            } else if set.contains(AttrSet::INLINE_HINT) {
275                Kind::Hinted
276            } else {
277                return None;
278            };
279            Some((func.name, (id, kind)))
280        })
281        .collect();
282    let mut done = Vec::new();
283    if !wanted.is_empty() {
284        let convention = Convention::of(module);
285        let mut state = HashMap::new();
286        let limit = limit.map_or(0, |limit| usize::try_from(limit).unwrap_or(usize::MAX));
287        let how = How { wanted: &wanted, convention, limit, isa };
288        for id in module.funcs().collect::<Vec<FuncId>>() {
289            settle(module, id, &how, &mut state, &mut done);
290        }
291        let (calls, elsewhere) = references(module);
292        for &(id, kind) in wanted.values() {
293            let func = &module[id];
294            let name = func.name;
295            let gone = match kind {
296                Kind::Once => true,
297                Kind::Always => {
298                    func.linkage == Linkage::Internal && !func.attrs.set.contains(AttrSet::USED)
299                }
300                Kind::Hinted => false,
301            };
302            if gone && !calls.contains_key(&name) && !elsewhere.contains(&name) {
303                module[id] = declaration(&module[id]);
304            }
305        }
306        for &(id, _) in &done {
307            settle_operands(&mut module[id]);
308        }
309    }
310    withdraw(module);
311    done
312}
313
314/// Every operand of an assembly statement in that function that is arithmetic over constants,
315/// written down as the constant.
316///
317/// Only an inlined call can leave one, since the front end folds a constant expression it hands
318/// to a statement itself. What it cannot fold is `_mm_round_ps (x, _MM_FROUND_TO_NEAREST_INT |
319/// _MM_FROUND_NO_EXC)`, where the expression is an argument and the statement is in the callee,
320/// and at `-O0` nothing after this folds it either, so an `i` operand would reach the back end
321/// as a register. gcc folds it at every level. The instruction that computes the operand becomes
322/// the constant where it stands, which is [`crate::fold`]'s rewrite, and so is right for every
323/// other use of it too.
324fn settle_operands(func: &mut Func) {
325    let mut found = Vec::new();
326    for block in func.blocks() {
327        for inst in func.insts(block) {
328            if func[inst].opcode != Opcode::InlineAsm {
329                continue;
330            }
331            for &arg in &func[func[inst].args] {
332                let Def::Result { inst: def, .. } = func[arg].def else { continue };
333                if matches!(func[def].opcode, Opcode::IConst | Opcode::FConst) {
334                    continue;
335                }
336                if let Some((imm, _)) = crate::fold::evaluated(func, arg, 8) {
337                    found.push((def, imm));
338                }
339            }
340        }
341    }
342    for (def, imm) in found {
343        let at = func.add_imm(imm);
344        let data = &mut func[def];
345        data.opcode = Opcode::IConst;
346        data.flags = rucc_ir::Flags::NONE;
347        data.args = ValueList::EMPTY;
348        data.extra = Extra::Imm(at);
349    }
350}
351
352/// The names the module reaches by exactly one direct call and in no other way.
353///
354/// Any other way is a second call, a tail call, an instruction that takes the address, an image
355/// that holds it, or an alias that names it. Whether a name is a `static` function that may be
356/// inlined is for the caller to ask.
357fn called_once(module: &Module) -> HashSet<Symbol> {
358    let (calls, elsewhere) = references(module);
359    calls
360        .into_iter()
361        .filter(|&(name, count)| count == 1 && !elsewhere.contains(&name))
362        .map(|(name, _)| name)
363        .collect()
364}
365
366/// How many direct calls the module makes to each name, and the names it reaches any other way.
367fn references(module: &Module) -> (HashMap<Symbol, usize>, HashSet<Symbol>) {
368    let mut calls: HashMap<Symbol, usize> = HashMap::new();
369    let mut elsewhere = HashSet::new();
370    for id in module.funcs() {
371        let func = &module[id];
372        for inst in func.blocks().flat_map(|block| func.insts(block)) {
373            match func[inst].extra {
374                Extra::Call(info) if func[inst].opcode == Opcode::Call => {
375                    if let Some(callee) = func[info].callee {
376                        *calls.entry(callee).or_default() += 1;
377                    }
378                }
379                Extra::Call(info) => elsewhere.extend(func[info].callee),
380                Extra::Symbol(name) => {
381                    elsewhere.insert(name);
382                }
383                _ => {}
384            }
385        }
386    }
387    for id in module.globals() {
388        let init = module[id].init.map(|list| &module[list]).unwrap_or_default();
389        for datum in init {
390            if let Datum::Addr(reloc) | Datum::Away(reloc) | Datum::Apart { to: reloc, .. } = *datum
391            {
392                elsewhere.insert(module[reloc].symbol);
393            }
394        }
395    }
396    for id in module.aliases() {
397        elsewhere.insert(module[id].target);
398    }
399    (calls, elsewhere)
400}
401
402/// What stays the same for every function [`settle`] visits.
403struct How<'a> {
404    /// The functions whose calls are inlined, by name, and why.
405    wanted: &'a HashMap<Symbol, (FuncId, Kind)>,
406    /// The calling convention the pack is forwarded under.
407    convention: Convention,
408    /// How many instructions a callee declared `inline` may have.
409    limit: usize,
410    /// What a function without a `target` attribute of its own is built for.
411    isa: Isa,
412}
413
414/// Where a function is in being settled.
415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
416enum State {
417    /// Its calls are being inlined, so a call back to it from one of them is a cycle.
418    Settling,
419    /// Every call of this kind in it that can be inlined has been.
420    Settled,
421}
422
423/// Inlines the `always_inline` calls in one function, settling each callee first.
424fn settle(
425    module: &mut Module,
426    id: FuncId,
427    how: &How<'_>,
428    state: &mut HashMap<FuncId, State>,
429    done: &mut Vec<(FuncId, Stats)>,
430) {
431    if state.contains_key(&id) || module[id].is_declaration() {
432        return;
433    }
434    state.insert(id, State::Settling);
435    // The calls as the function was written. A call that arrives inside a body being inlined is
436    // one the callee's own settling already had its chance at. A function that asked not to be
437    // optimized is left with its calls, except for the ones that are a promise.
438    let optnone = module[id].attrs.set.contains(AttrSet::OPTNONE);
439    let calls: Vec<(Block, Inst, FuncId, Kind)> = {
440        let func = &module[id];
441        func.blocks()
442            .flat_map(|block| func.insts(block).map(move |inst| (block, inst)))
443            .filter_map(|(block, inst)| {
444                let Extra::Call(info) = func[inst].extra else { return None };
445                if func[inst].opcode != Opcode::Call {
446                    return None;
447                }
448                let callee = func[info].callee?;
449                let &(callee, kind) = how.wanted.get(&callee)?;
450                (kind == Kind::Always || !optnone).then_some((block, inst, callee, kind))
451            })
452            .collect()
453    };
454    // The calls to a function called once that are too many loops deep, found before anything is
455    // spliced in, since a splice splits the block the call was in. Counted as gcc counts, so a
456    // block in one loop is one deep, which is one more than `Loops::depth` says.
457    let deep: HashSet<Inst> = if calls.iter().any(|&(.., kind)| kind == Kind::Once) {
458        let func = &module[id];
459        let cfg = Cfg::new(func);
460        let loops = Loops::new(&cfg, &Dominators::new(&cfg));
461        let depth = |block| loops.innermost(block).map_or(0, |inner| loops.depth(inner) + 1);
462        calls
463            .iter()
464            .filter(|&&(block, _, _, kind)| {
465                kind == Kind::Once && depth(block) > INLINE_CALLED_ONCE_LOOP_DEPTH
466            })
467            .map(|&(_, inst, ..)| inst)
468            .collect()
469    } else {
470        HashSet::new()
471    };
472    let mut stats = Stats::new();
473    for (_, call, callee, kind) in calls {
474        let why = |failure: InlineFailure| match kind {
475            Kind::Always => failure.why(),
476            Kind::Hinted => failure.hint(),
477            Kind::Once => failure.once(),
478        };
479        if callee == id || state.get(&callee) == Some(&State::Settling) {
480            stats.missed(why(InlineFailure::Recursive));
481            continue;
482        }
483        if deep.contains(&call) {
484            stats.missed(why(InlineFailure::TooDeep));
485            continue;
486        }
487        // A body built for more than the caller cannot go into it, whatever else is true of the
488        // call, so this is asked before anything is settled or measured.
489        if let Some(wanted) = module[callee].target {
490            if !module[id].target.unwrap_or(how.isa).covers(wanted) {
491                stats.missed(why(InlineFailure::Target));
492                continue;
493            }
494        }
495        settle(module, callee, how, state, done);
496        // Measured once the callee is settled, since what is copied is the body with its own
497        // calls already inlined.
498        let most = match kind {
499            Kind::Always => usize::MAX,
500            Kind::Hinted => how.limit,
501            Kind::Once => INLINE_CALLED_ONCE_INSNS as usize,
502        };
503        if size(&module[callee]) > most {
504            stats.missed(why(InlineFailure::TooLarge));
505            continue;
506        }
507        match splice(module, id, call, callee, how.convention, kind) {
508            Ok(()) => stats.optimized(match kind {
509                Kind::Always => INLINED,
510                Kind::Hinted => HINT_INLINED,
511                Kind::Once => ONCE_INLINED,
512            }),
513            Err(failure) => stats.missed(why(failure)),
514        }
515    }
516    state.insert(id, State::Settled);
517    if !stats.is_empty() {
518        done.push((id, stats));
519    }
520}
521
522/// How many instructions a body has, which is what the limit on a callee declared `inline` counts.
523fn size(func: &Func) -> usize {
524    func.blocks().map(|block| func.insts(block).count()).sum()
525}
526
527/// Inlines one call, or says why not and leaves the caller as it was.
528fn splice(
529    module: &mut Module,
530    caller: FuncId,
531    call: Inst,
532    callee: FuncId,
533    convention: Convention,
534    kind: Kind,
535) -> Result<(), InlineFailure> {
536    // Out of the module for the length of the splice, so that the callee can be read while the
537    // caller is written. The two are different functions, since a call to itself is refused
538    // before this.
539    let stand_in = Func::new(module[caller].name, Signature::new());
540    let mut func = std::mem::replace(&mut module[caller], stand_in);
541    let result = check(&func, call, &module[callee], convention, kind)
542        .map(|plan| copy(&mut func, call, &module[callee], &plan));
543    module[caller] = func;
544    result
545}
546
547/// What [`check`] found out that [`copy`] needs.
548struct Plan {
549    /// How many of the call's arguments go to the callee's entry block.
550    fixed: usize,
551    /// The rest of them, which are what a `va_arg_pack` stands for.
552    extras: Vec<Value>,
553    /// How each of those travels, one for each.
554    abis: Vec<Abi>,
555    /// How many of them each C argument became, where the lowering said.
556    groups: Option<Vec<u32>>,
557    /// For each call in the callee that passes the pack on, the groups that have to go to memory
558    /// because the registers they went in are taken there, which is only ever under SysV.
559    spills: HashMap<Inst, Vec<usize>>,
560}
561
562/// Whether one call can be inlined, and what the splice needs to know if it can.
563fn check(
564    func: &Func,
565    call: Inst,
566    callee: &Func,
567    convention: Convention,
568    kind: Kind,
569) -> Result<Plan, InlineFailure> {
570    // The pad is for an unwind out of this call, and the table that says so names the call by
571    // where it is. A copy of the body in its place is calls the table knows nothing about, so
572    // `copy` gives each of them the same pad, which needs the pad to be found.
573    if func.unwinds_to_pad(call) && unwind_arms(func, call).is_none() {
574        return Err(InlineFailure::Unwinds);
575    }
576    let entry = callee.entry().ok_or(InlineFailure::Mismatch)?;
577    let params = &callee[entry].params;
578    let args = &func[func[call].args];
579    let Extra::Call(info) = func[call].extra else { return Err(InlineFailure::Mismatch) };
580    let signature = &func[func[info].signature];
581    if args.len() < params.len()
582        || (args.len() > params.len() && !callee.signature().variadic)
583        || args.iter().zip(params).any(|(&arg, &param)| func[arg].ty != callee[param].ty)
584    {
585        return Err(InlineFailure::Mismatch);
586    }
587    let returns: Vec<Type> = callee.signature().return_types().collect();
588    let results: Vec<Type> = func[call].results().map(|value| func[value].ty).collect();
589    if results.len() > returns.len() || results.iter().zip(&returns).any(|(a, b)| a != b) {
590        return Err(InlineFailure::Mismatch);
591    }
592    // An `always_inline` function that takes a structure or a vector by value is inlined with a
593    // copy of the argument in the caller, which is what the call would have made. The intrinsics
594    // over 64 byte vectors are all of this kind, and gcc inlines them at every level. Other calls
595    // are still left alone, since that changes which calls are inlined across a whole program.
596    if kind != Kind::Always
597        && callee.signature().params.iter().any(|param| matches!(param.abi, Abi::ByVal { .. }))
598    {
599        return Err(InlineFailure::ByValue);
600    }
601
602    let fixed = params.len();
603    let extras = args[fixed..].to_vec();
604    let abis = expand(&func[func[info].varargs], extras.len());
605    let groups = func.arg_groups(call).and_then(|groups| past(groups, fixed));
606    let mut plan = Plan { fixed, extras, abis, groups, spills: HashMap::new() };
607    let outer: Vec<(Type, Abi)> = args[..fixed]
608        .iter()
609        .enumerate()
610        .map(|(at, &arg)| (func[arg].ty, signature.params.get(at).map_or(Abi::Plain, |p| p.abi)))
611        .collect();
612
613    if callee.named_blocks().next().is_some() {
614        return Err(InlineFailure::ComputedGoto);
615    }
616    let mut packs = HashSet::new();
617    let mut counted = false;
618    for block in callee.blocks() {
619        for inst in callee.insts(block) {
620            match callee[inst].opcode {
621                Opcode::VaStart => return Err(InlineFailure::VaStart),
622                Opcode::IndirectBr => return Err(InlineFailure::ComputedGoto),
623                Opcode::Alloca if kind != Kind::Always && !callee[inst].args.is_empty() => {
624                    return Err(InlineFailure::Alloca);
625                }
626                Opcode::SetjmpMarker => return Err(InlineFailure::Setjmp),
627                Opcode::ApplyArgs => return Err(InlineFailure::ApplyArgs),
628                Opcode::MemEntry => return Err(InlineFailure::MemorySsa),
629                Opcode::VaArgPack => packs.extend(callee[inst].results()),
630                Opcode::VaArgPackLen => counted = true,
631                _ => {}
632            }
633        }
634    }
635    // A pack standing for another pack is the caller being an inline definition itself, and
636    // what that pack stands for, or how many it is, is not known until the caller is inlined
637    // somewhere.
638    if (counted || !packs.is_empty()) && plan.extras.iter().any(|&value| is_pack(func, value)) {
639        return Err(InlineFailure::Pack);
640    }
641    if packs.is_empty() {
642        return Ok(plan);
643    }
644    for block in callee.blocks() {
645        for inst in callee.insts(block) {
646            let data = &callee[inst];
647            let used = callee[data.args].iter().position(|value| packs.contains(value));
648            let passed = callee
649                .successors(inst)
650                .any(|to| callee[to.args].iter().any(|value| packs.contains(value)));
651            if passed {
652                return Err(InlineFailure::Pack);
653            }
654            let Some(at) = used else { continue };
655            let args = &callee[data.args];
656            let Extra::Call(inner) = data.extra else { return Err(InlineFailure::Pack) };
657            if at + 1 != args.len() || !matches!(data.opcode, Opcode::Call | Opcode::CallIndirect) {
658                return Err(InlineFailure::Pack);
659            }
660            let skip = usize::from(data.opcode == Opcode::CallIndirect);
661            let named = &callee[callee[inner].signature].params;
662            let written = &args[skip..at];
663            let anonymous = expand(&callee[callee[inner].varargs], written.len() + 1 - named.len());
664            let before: Vec<(Type, Abi)> = written
665                .iter()
666                .enumerate()
667                .map(|(index, &value)| {
668                    let abi = match named.get(index) {
669                        Some(param) => param.abi,
670                        None => anonymous[index - named.len()],
671                    };
672                    (callee[value].ty, abi)
673                })
674                .collect();
675            let forwarded: Vec<(Type, Abi)> = plan
676                .extras
677                .iter()
678                .zip(&plan.abis)
679                .map(|(&value, &abi)| (func[value].ty, abi))
680                .collect();
681            let spills =
682                forwardable(convention, &outer, &before, &forwarded, plan.groups.as_deref())
683                    .ok_or(InlineFailure::Pack)?;
684            if !spills.is_empty() {
685                plan.spills.insert(inst, spills);
686            }
687        }
688    }
689    Ok(plan)
690}
691
692/// Whether a value is what a `va_arg_pack` produced.
693fn is_pack(func: &Func, value: Value) -> bool {
694    matches!(func[value].def, Def::Result { inst, .. } if func[inst].opcode == Opcode::VaArgPack)
695}
696
697/// A list of how the anonymous arguments travel, with the empty one that means every one of them
698/// is plain written out.
699fn expand(abis: &[Abi], count: usize) -> Vec<Abi> {
700    if abis.is_empty() { vec![Abi::Plain; count] } else { abis.to_vec() }
701}
702
703/// The groups past the first `fixed` values, or `None` when a group straddles that point, which
704/// no lowering does.
705fn past(groups: &[u32], fixed: usize) -> Option<Vec<u32>> {
706    let mut seen = 0;
707    let mut rest = groups.iter();
708    while seen < fixed {
709        seen += usize::try_from(*rest.next()?).ok()?;
710    }
711    (seen == fixed).then(|| rest.copied().collect())
712}
713
714/// The calling convention, as far as forwarding arguments from one call to another cares.
715#[derive(Debug, Clone, Copy, PartialEq, Eq)]
716enum Convention {
717    /// x86-64 System V, where a structure goes in registers whole or not at all.
718    SysV,
719    /// Windows x64, where every argument is one slot and nothing depends on what came before.
720    Slots,
721    /// Everything else, where the answer is only trusted when nothing moves.
722    Other,
723}
724
725impl Convention {
726    fn of(module: &Module) -> Self {
727        match (module.tuple.arch(), module.tuple.os()) {
728            (Arch::X86_64, Os::Windows) => Self::Slots,
729            (Arch::X86_64, _) => Self::SysV,
730            _ => Self::Other,
731        }
732    }
733}
734
735/// Where one value goes under SysV, as far as registers are concerned.
736#[derive(Debug, Clone, Copy, PartialEq, Eq)]
737enum Class {
738    /// General purpose registers, this many of them.
739    Gpr(u32),
740    /// One vector register.
741    Sse,
742    /// The argument area, whatever registers are left.
743    Memory,
744}
745
746fn class(ty: Type, abi: Abi) -> Class {
747    if abi.indirect() && !matches!(abi, Abi::Sret { .. }) {
748        Class::Memory
749    } else if ty.is_vector() {
750        Class::Sse
751    } else if ty.is_float() {
752        if ty.format() == Some(Float::F80) { Class::Memory } else { Class::Sse }
753    } else if ty.is_int() && ty.bits() > 64 {
754        Class::Gpr(2)
755    } else {
756        Class::Gpr(1)
757    }
758}
759
760/// The registers a SysV call has used so far.
761#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
762struct Regs {
763    gpr: u32,
764    sse: u32,
765}
766
767impl Regs {
768    const GPR: u32 = 6;
769    const SSE: u32 = 8;
770
771    fn after(values: &[(Type, Abi)]) -> Self {
772        let mut regs = Self::default();
773        for &(ty, abi) in values {
774            regs.take(class(ty, abi));
775        }
776        regs
777    }
778
779    fn fits(self, gpr: u32, sse: u32) -> bool {
780        self.gpr + gpr <= Self::GPR && self.sse + sse <= Self::SSE
781    }
782
783    /// Takes what one value of that class needs, if there is room, and says whether there was.
784    fn take(&mut self, class: Class) -> bool {
785        let (gpr, sse) = match class {
786            Class::Gpr(count) => (count, 0),
787            Class::Sse => (0, 1),
788            Class::Memory => return false,
789        };
790        let room = self.fits(gpr, sse);
791        if room {
792            self.gpr += gpr;
793            self.sse += sse;
794        }
795        room
796    }
797}
798
799/// Whether the anonymous arguments of one call can be passed on to another, and which of them
800/// have to go to memory on the way.
801///
802/// `outer` is what the first call passes to the named parameters, `before` is what the second
803/// passes ahead of the pack, and `forwarded` is what the pack stands for, all as the lowering left
804/// them. The answer is yes when the second call has used the same registers as the first by the
805/// time the pack starts, since then every argument goes where it went. Under SysV it is also yes
806/// when the registers used differ but no argument that decides where it goes as a whole would
807/// decide differently, which is a small structure in memory that would now fit. A structure in
808/// registers that no longer fits goes to memory, as it would have if the second call had been
809/// written out, and the answer says which groups those are. `groups` is what says which values
810/// are one structure, and without it only the first answer is given.
811fn forwardable(
812    convention: Convention,
813    outer: &[(Type, Abi)],
814    before: &[(Type, Abi)],
815    forwarded: &[(Type, Abi)],
816    groups: Option<&[u32]>,
817) -> Option<Vec<usize>> {
818    match convention {
819        Convention::Slots => Some(Vec::new()),
820        Convention::Other => {
821            let count = |values: &[(Type, Abi)]| {
822                let mut ints = 0;
823                let mut floats = 0;
824                for &(ty, abi) in values {
825                    if abi.indirect() {
826                        return None;
827                    }
828                    if ty.is_float() || ty.is_vector() { floats += 1 } else { ints += 1 }
829                }
830                Some((ints, floats))
831            };
832            (count(outer).is_some() && count(outer) == count(before)).then(Vec::new)
833        }
834        Convention::SysV => {
835            let mut first = Regs::after(outer);
836            let mut second = Regs::after(before);
837            if first == second {
838                return Some(Vec::new());
839            }
840            let mut spills = Vec::new();
841            let mut at = 0;
842            for (index, &count) in groups?.iter().enumerate() {
843                let group = usize::try_from(count).ok().and_then(|n| forwarded.get(at..at + n))?;
844                at += group.len();
845                match *group {
846                    [] => {}
847                    // One value, which goes wherever the registers left send it in either call,
848                    // except for a small structure in memory. That may be there because it did
849                    // not fit, and if the second call has more room it would be in registers.
850                    [(ty, abi)] => {
851                        if let Abi::ByVal { size, .. } = abi {
852                            let small = size <= 16; // not a threshold: the SysV register limit
853                            if small && (second.gpr < first.gpr || second.sse < first.sse) {
854                                return None;
855                            }
856                            continue;
857                        }
858                        first.take(class(ty, abi));
859                        second.take(class(ty, abi));
860                    }
861                    // A structure in registers, which the first call found room for. It goes in
862                    // the second one's registers if there is room, and to memory if not.
863                    _ => {
864                        let mut gpr = 0;
865                        let mut sse = 0;
866                        for &(ty, abi) in group {
867                            match class(ty, abi) {
868                                Class::Gpr(count) => gpr += count,
869                                Class::Sse => sse += 1,
870                                Class::Memory => return None,
871                            }
872                        }
873                        if !first.fits(gpr, sse) {
874                            return None;
875                        }
876                        first.gpr += gpr;
877                        first.sse += sse;
878                        if second.fits(gpr, sse) {
879                            second.gpr += gpr;
880                            second.sse += sse;
881                        } else {
882                            spills.push(index);
883                        }
884                    }
885                }
886            }
887            (at == forwarded.len()).then_some(spills)
888        }
889    }
890}
891
892/// Splices the callee in where the call is, which [`check`] has said it can be.
893fn copy(func: &mut Func, call: Inst, callee: &Func, plan: &Plan) {
894    let block = func.block_of(call).expect("a call being inlined is in a block");
895    let entry = func.entry().expect("a function with a call in it has a body");
896    // Where an unwind out of the call went, and where a return from it went, when a `cleanup`
897    // handler's scope gave it a pad. Read before the block is split, since the split moves the
898    // branch that says so.
899    let arms = unwind_arms(func, call);
900
901    // The part after the call, which takes the call's results as parameters.
902    let after = func.create_block();
903    let mut forward = HashMap::new();
904    for result in func[call].results().collect::<Vec<Value>>() {
905        let ty = func[result].ty;
906        forward.insert(result, func.append_param(after, ty));
907    }
908    let moving: Vec<Inst> = func.insts(block).skip_while(|&inst| inst != call).skip(1).collect();
909    for inst in moving {
910        func.remove_inst(inst);
911        func.append_inst(after, inst);
912    }
913    // With the call gone the `unwound` behind it has nothing to ask about, and the branch on it
914    // goes where a return went, always.
915    if let Some((unwound, branch, _, returned)) = arms {
916        func.remove_inst(unwound);
917        func.remove_inst(branch);
918        let args = func[returned.args].to_vec();
919        let args = func.push_values(&args);
920        let to = func.push_block_calls(&[BlockCall { args, ..returned }]);
921        let span = func.span(branch);
922        let jump = func.create_inst(
923            InstData { extra: Extra::Targets(to), ..InstData::new(Opcode::Jump) },
924            &[],
925            span,
926        );
927        func.append_inst(after, jump);
928    }
929
930    // The callee's blocks and their parameters, and then its instructions with their results, so
931    // that every value exists before any operand is written.
932    //
933    // The entry block's parameters are the call's arguments themselves rather than parameters of
934    // the copy, since nothing branches to an entry block and so nothing else arrives there. That
935    // way a constant argument is a constant in the body straight away, and the folding that runs
936    // next sees `1 + 1` rather than a block parameter that only `simplify-cfg` would later find
937    // is always `1`.
938    let start = callee.entry().expect("checked to have a body");
939    let mut passed = func[func[call].args][..plan.fixed].to_vec();
940    // An argument passed by value is the callee's own copy, which it may write to, so the body
941    // gets a copy made in the caller's frame just before the call, as the call would have.
942    for (at, param) in callee.signature().params.iter().enumerate().take(plan.fixed) {
943        if let Abi::ByVal { size, align, .. } = param.abi {
944            passed[at] = by_value(func, entry, call, passed[at], size, align);
945        }
946    }
947    let mut blocks = HashMap::new();
948    let mut values = HashMap::new();
949    for from in callee.blocks() {
950        let to = func.create_block();
951        if from == start {
952            values.extend(callee[from].params.iter().copied().zip(passed.iter().copied()));
953        } else {
954            for &param in &callee[from].params {
955                values.insert(param, func.append_param(to, callee[param].ty));
956            }
957        }
958        blocks.insert(from, to);
959    }
960    // The lowering gives the callee's slots and the stores of its parameters the span of its
961    // whole body, which the line table reads as the line of the opening brace. That is the right
962    // answer in the callee's own prologue and the wrong one here: that line is outside the caller,
963    // and a line table that names it inside the caller sends a debugger to the top of another
964    // function. What those instructions do in the caller is take the arguments of the call, so
965    // they say the call instead, which is what gcc says for them. Every statement in the callee
966    // keeps its own place, and a callee with no body span, which is one the IR parser built, keeps
967    // every span it has.
968    let at = func.span(call);
969    let body = callee.declared;
970    let spliced = |inst: Inst| {
971        let span = callee.span(inst);
972        let prologue = span == body || !body.contains(span.lo);
973        if span.is_dummy() || body.is_dummy() || !prologue { span } else { at }
974    };
975    let mut made = Vec::new();
976    for from in callee.blocks() {
977        for inst in callee.insts(from) {
978            let data = &callee[inst];
979            if data.opcode == Opcode::VaArgPack {
980                continue;
981            }
982            let opcode = match data.opcode {
983                Opcode::Return => Opcode::Jump,
984                Opcode::VaArgPackLen => Opcode::IConst,
985                opcode => opcode,
986            };
987            let types: Vec<Type> = data.results().map(|value| callee[value].ty).collect();
988            let shell = InstData { flags: data.flags, ..InstData::new(opcode) };
989            let fixed = opcode == Opcode::Alloca && data.args.is_empty();
990            let first = func.insts(entry).next().expect("an entry block ends in something");
991            let span = if fixed {
992                // A slot joins the caller's frame, so it says what the caller's own slots say.
993                func.span(first)
994            } else {
995                spliced(inst)
996            };
997            let new = func.create_inst(shell, &types, span);
998            for (old, value) in data.results().zip(func[new].results().collect::<Vec<Value>>()) {
999                values.insert(old, value);
1000            }
1001            if fixed {
1002                func.insert_before(new, first);
1003            } else {
1004                func.append_inst(blocks[&from], new);
1005            }
1006            made.push((inst, new));
1007        }
1008    }
1009
1010    // The calls of the body that an unwind leaves with no pad of the callee's to run, which are
1011    // the ones given the caller's below. A call with a pad of its own already goes somewhere, and
1012    // that pad ends in `_Unwind_Resume`, which is one of these.
1013    let bare: Vec<Inst> = made
1014        .iter()
1015        .filter(|&&(inst, _)| {
1016            matches!(callee[inst].opcode, Opcode::Call | Opcode::CallIndirect)
1017                && !callee.unwinds_to_pad(inst)
1018        })
1019        .map(|&(_, new)| new)
1020        .collect();
1021
1022    let keep = func[call].results().count();
1023    for (inst, new) in made {
1024        let data = &callee[inst];
1025        let mut args: Vec<Value> = callee[data.args]
1026            .iter()
1027            .filter(|value| values.contains_key(value))
1028            .map(|value| values[value])
1029            .collect();
1030        let packed = args.len() != data.args.len();
1031        let extra = if data.opcode == Opcode::Return {
1032            args.truncate(keep);
1033            let to = func.push_values(&args);
1034            args.clear();
1035            Extra::Targets(func.push_block_calls(&[BlockCall::new(after, to)]))
1036        } else if data.opcode == Opcode::VaArgPackLen {
1037            // How many C arguments the pack stands for, which is the groups where the lowering
1038            // said and one value each where it did not.
1039            let count = plan.groups.as_ref().map_or(plan.extras.len(), Vec::len);
1040            let count = i128::try_from(count).expect("fewer arguments than that");
1041            Extra::Imm(func.add_imm(Imm::int(count, Type::int(32))))
1042        } else {
1043            match data.extra {
1044                Extra::Imm(imm) => Extra::Imm(func.add_imm(callee[imm])),
1045                Extra::Mem(mem) => Extra::Mem(func.add_mem(unscoped(callee[mem]))),
1046                Extra::Rmw(op, mem) => Extra::Rmw(op, func.add_mem(unscoped(callee[mem]))),
1047                Extra::Targets(list) => {
1048                    Extra::Targets(targets(func, callee, list, &blocks, &values))
1049                }
1050                Extra::Call(info) => {
1051                    let info = callee[info];
1052                    let mut forwarded = None;
1053                    let signature = callee[info.signature].clone();
1054                    let mut abis = callee[info.varargs].to_vec();
1055                    if packed {
1056                        let skip = usize::from(data.opcode == Opcode::CallIndirect);
1057                        let written = args.len() - skip - signature.params.len();
1058                        abis = expand(&abis, written + 1);
1059                        abis.truncate(written);
1060                        let spills = plan.spills.get(&inst).map_or(&[][..], Vec::as_slice);
1061                        forwarded = pass_on(func, entry, new, spills, plan, &mut args, &mut abis);
1062                        if abis.iter().all(|&abi| abi == Abi::Plain) {
1063                            abis.clear();
1064                        }
1065                    }
1066                    let signature = func.add_signature(signature);
1067                    let varargs = func.push_abis(&abis);
1068                    if let Some(groups) = callee.arg_groups(inst) {
1069                        let mut groups = groups.to_vec();
1070                        let known = if packed {
1071                            groups.pop();
1072                            forwarded.as_ref().map(|outer| groups.extend_from_slice(outer))
1073                        } else {
1074                            Some(())
1075                        };
1076                        if known.is_some() {
1077                            func.set_arg_groups(new, groups);
1078                        }
1079                    }
1080                    Extra::Call(func.add_call(CallInfo { callee: info.callee, signature, varargs }))
1081                }
1082                Extra::Switch(info) => {
1083                    let info = callee[info];
1084                    let cases = func.push_imms(&callee[info.cases]);
1085                    let targets = targets(func, callee, info.targets, &blocks, &values);
1086                    Extra::Switch(func.add_switch(SwitchInfo { targets, cases }))
1087                }
1088                Extra::Asm(info) => {
1089                    let info = callee[info];
1090                    let targets = targets(func, callee, info.targets, &blocks, &values);
1091                    Extra::Asm(func.add_asm(AsmInfo { targets, ..info }))
1092                }
1093                Extra::VaObject(info) => {
1094                    let info = callee[info];
1095                    let mem = func.add_mem(unscoped(callee[info.mem]));
1096                    let slots = func.push_slots(&callee[info.slots]);
1097                    Extra::VaObject(func.add_va_object(VaInfo { mem, slots }))
1098                }
1099                other => other,
1100            }
1101        };
1102        func[new].args = if args.is_empty() { ValueList::EMPTY } else { func.push_values(&args) };
1103        func[new].extra = extra;
1104    }
1105
1106    // And the call itself, which becomes a jump to the copy of the entry block.
1107    let to = ValueList::EMPTY;
1108    let targets = func.push_block_calls(&[BlockCall::new(blocks[&start], to)]);
1109    let span = func.span(call);
1110    let jump = func.create_inst(
1111        InstData { extra: Extra::Targets(targets), ..InstData::new(Opcode::Jump) },
1112        &[],
1113        span,
1114    );
1115    crate::uses::substitute(func, &forward);
1116    func.remove_inst(call);
1117    func.append_inst(block, jump);
1118
1119    // An unwind out of any of those calls passes through the call that was inlined, so it owes
1120    // what that call's pad does. Each one gets the edge the lowering gives a call in a handler's
1121    // scope, an `unwound` and a branch on it to the pad, with the rest of its block moved behind
1122    // the branch. Several calls sharing one pad is fine, since the code generator finds a call's
1123    // pad by its branch and no machine edge ever enters one.
1124    if let Some((_, _, pad, _)) = arms {
1125        for new in bare {
1126            let block = func.block_of(new).expect("a copied call is in a block");
1127            let rest = func.create_block();
1128            let moving: Vec<Inst> =
1129                func.insts(block).skip_while(|&inst| inst != new).skip(1).collect();
1130            for inst in moving {
1131                func.remove_inst(inst);
1132                func.append_inst(rest, inst);
1133            }
1134            let span = func.span(new);
1135            let unwound = func.create_inst(InstData::new(Opcode::Unwound), &[Type::I1], span);
1136            func.append_inst(block, unwound);
1137            let cond = func[unwound].results().next().expect("an unwound has its answer");
1138            let args = func[pad.args].to_vec();
1139            let args = func.push_values(&args);
1140            let to = func.push_block_calls(&[
1141                BlockCall { args, ..pad },
1142                BlockCall::new(rest, ValueList::EMPTY),
1143            ]);
1144            let branch = func.create_inst(
1145                InstData { extra: Extra::Targets(to), ..InstData::new(Opcode::BrIf) },
1146                &[],
1147                span,
1148            );
1149            func[branch].args = func.push_values(&[cond]);
1150            func.append_inst(block, branch);
1151        }
1152    }
1153}
1154
1155/// The `unwound` behind a call with a pad, the branch on it that ends the call's block, and the
1156/// branch's two arms, the pad first and then where a return goes. `None` for a call with no pad,
1157/// and for one whose edge is not in the shape the lowering builds.
1158fn unwind_arms(func: &Func, call: Inst) -> Option<(Inst, Inst, BlockCall, BlockCall)> {
1159    let unwound = func.next_inst(call).filter(|&next| func[next].opcode == Opcode::Unwound)?;
1160    let block = func.block_of(call)?;
1161    let branch = func.insts(block).last()?;
1162    let answer = func[unwound].results().next()?;
1163    if func[branch].opcode != Opcode::BrIf || func[func[branch].args].first() != Some(&answer) {
1164        return None;
1165    }
1166    let Extra::Targets(list) = func[branch].extra else { return None };
1167    match func[list] {
1168        [pad, returned] => Some((unwound, branch, pad, returned)),
1169        _ => None,
1170    }
1171}
1172
1173/// Appends what the pack stands for to the arguments of one call, putting each group the plan
1174/// says has to go to memory in a slot of the caller's that the call copies from, and gives back
1175/// the groups as they are after that.
1176fn pass_on(
1177    func: &mut Func,
1178    entry: Block,
1179    call: Inst,
1180    spills: &[usize],
1181    plan: &Plan,
1182    args: &mut Vec<Value>,
1183    abis: &mut Vec<Abi>,
1184) -> Option<Vec<u32>> {
1185    let Some(groups) = plan.groups.as_deref().filter(|_| !spills.is_empty()) else {
1186        args.extend_from_slice(&plan.extras);
1187        abis.extend_from_slice(&plan.abis);
1188        return plan.groups.clone();
1189    };
1190    let mut now = Vec::with_capacity(groups.len());
1191    let mut at = 0;
1192    for (index, &count) in groups.iter().enumerate() {
1193        let end = at + count as usize;
1194        if spills.contains(&index) {
1195            let (slot, size) = spill(func, entry, call, &plan.extras[at..end]);
1196            args.push(slot);
1197            abis.push(Abi::ByVal { size, align: 8, drains: Drains::Nothing });
1198            now.push(1);
1199        } else {
1200            args.extend_from_slice(&plan.extras[at..end]);
1201            abis.extend_from_slice(&plan.abis[at..end]);
1202            now.push(count);
1203        }
1204        at = end;
1205    }
1206    Some(now)
1207}
1208
1209/// A slot of `size` bytes in the caller's frame with the object `from` points at copied into it
1210/// just before the call, which is what a call passing that object by value makes.
1211fn by_value(
1212    func: &mut Func,
1213    entry: Block,
1214    call: Inst,
1215    from: Value,
1216    size: u64,
1217    align: u32,
1218) -> Value {
1219    let span = func.span(call);
1220    let info = MemInfo {
1221        size,
1222        align,
1223        order: MemOrder::NotAtomic,
1224        tbaa: None,
1225        owns: 0,
1226        restrict: Restrict::NONE,
1227    };
1228    let mem = func.add_mem(info);
1229    let alloca = InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) };
1230    let alloca = func.create_inst(alloca, &[Type::PTR], span);
1231    let first = func.insts(entry).next().expect("an entry block ends in something");
1232    func.insert_before(alloca, first);
1233    let slot = func[alloca].results().next().expect("an alloca has a result");
1234    let copy = InstData {
1235        args: func.push_values(&[slot, from]),
1236        extra: Extra::Mem(func.add_mem(info)),
1237        ..InstData::new(Opcode::Memcpy)
1238    };
1239    let copy = func.create_inst(copy, &[], span);
1240    func.insert_before(copy, call);
1241    slot
1242}
1243
1244/// Stores the pieces of one structure, eight bytes apart the way the registers held them, in a
1245/// new slot at the top of the caller, just ahead of the call, and gives back the slot and its size.
1246fn spill(func: &mut Func, entry: Block, call: Inst, pieces: &[Value]) -> (Value, u64) {
1247    let span = func.span(call);
1248    let bytes = |ty: Type| {
1249        if ty == Type::PTR { 8 } else { u64::from(ty.bits() * ty.lanes()).div_ceil(8) }
1250    };
1251    let size: u64 = pieces.iter().map(|&piece| bytes(func[piece].ty).next_multiple_of(8)).sum();
1252    let info = MemInfo {
1253        size,
1254        align: 8,
1255        order: MemOrder::NotAtomic,
1256        tbaa: None,
1257        owns: 0,
1258        restrict: Restrict::NONE,
1259    };
1260    let mem = func.add_mem(info);
1261    let alloca = InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) };
1262    let alloca = func.create_inst(alloca, &[Type::PTR], span);
1263    let first = func.insts(entry).next().expect("an entry block ends in something");
1264    func.insert_before(alloca, first);
1265    let slot = func[alloca].results().next().expect("an alloca has a result");
1266
1267    let mut offset = 0;
1268    for &piece in pieces {
1269        let ty = func[piece].ty;
1270        let width = bytes(ty);
1271        let mut address = slot;
1272        if offset != 0 {
1273            let imm = func.add_imm(Imm::int(i128::from(offset), Type::int(64)));
1274            let amount = InstData { extra: Extra::Imm(imm), ..InstData::new(Opcode::IConst) };
1275            let amount = func.create_inst(amount, &[Type::int(64)], span);
1276            func.insert_before(amount, call);
1277            let amount = func[amount].results().next().expect("a constant has a result");
1278            let add = InstData {
1279                args: func.push_values(&[slot, amount]),
1280                ..InstData::new(Opcode::PtrAdd)
1281            };
1282            let add = func.create_inst(add, &[Type::PTR], span);
1283            func.insert_before(add, call);
1284            address = func[add].results().next().expect("an address has a result");
1285        }
1286        let info = MemInfo { size: width, ..info };
1287        let store = InstData {
1288            args: func.push_values(&[piece, address]),
1289            extra: Extra::Mem(func.add_mem(info)),
1290            ..InstData::new(Opcode::Store)
1291        };
1292        let store = func.create_inst(store, &[], span);
1293        func.insert_before(store, call);
1294        offset += width.next_multiple_of(8);
1295    }
1296    (slot, size)
1297}
1298
1299/// An access as the callee described it, less the `restrict` scope, whose numbers are the
1300/// callee's and could mean a different scope in the caller.
1301fn unscoped(info: MemInfo) -> MemInfo {
1302    MemInfo { restrict: Restrict::NONE, ..info }
1303}
1304
1305/// A list of branch targets copied across, with the blocks and the arguments mapped.
1306fn targets(
1307    func: &mut Func,
1308    callee: &Func,
1309    list: BlockCallList,
1310    blocks: &HashMap<Block, Block>,
1311    values: &HashMap<Value, Value>,
1312) -> BlockCallList {
1313    let calls: Vec<BlockCall> = callee[list]
1314        .iter()
1315        .map(|call| {
1316            let args: Vec<Value> = callee[call.args].iter().map(|value| values[value]).collect();
1317            let args = func.push_values(&args);
1318            BlockCall { block: blocks[&call.block], args, hint: call.hint }
1319        })
1320        .collect();
1321    func.push_block_calls(&calls)
1322}
1323
1324/// Turns every function that still holds a `va_arg_pack` or a `va_arg_pack_len`, and every one
1325/// marked `inline_only`, into a declaration of the same name.
1326fn withdraw(module: &mut Module) {
1327    for id in module.funcs().collect::<Vec<FuncId>>() {
1328        let func = &module[id];
1329        if func.is_declaration() {
1330            continue;
1331        }
1332        let holds = func
1333            .blocks()
1334            .flat_map(|block| func.insts(block))
1335            .any(|inst| matches!(func[inst].opcode, Opcode::VaArgPack | Opcode::VaArgPackLen));
1336        if !holds && !func.attrs.set.contains(AttrSet::INLINE_ONLY) {
1337            continue;
1338        }
1339        module[id] = declaration(func);
1340    }
1341}
1342
1343/// A declaration of that function under the same name, with no body and nothing to emit.
1344fn declaration(func: &Func) -> Func {
1345    let mut declared = Func::new(func.name, func.signature().clone());
1346    declared.spelled = func.spelled;
1347    declared.visibility = func.visibility;
1348    declared.attrs = func.attrs;
1349    declared.target = func.target;
1350    declared.attrs.set = declared.attrs.set.without(AttrSet::INLINE_ONLY);
1351    declared.declared = func.declared;
1352    declared.linkage = Linkage::External;
1353    declared
1354}
1355
1356#[cfg(test)]
1357mod tests {
1358    use rucc_base::Interner;
1359    use rucc_diag::Span;
1360
1361    use super::*;
1362
1363    const HEAD: &str = r#"; ModuleID = 't.c'
1364; format 0
1365target triple = "x86_64-unknown-linux-gnu"
1366target datalayout = "e-p:64:64-i64:64-f80:128-S128"
1367"#;
1368
1369    fn inlined(body: &str) -> String {
1370        inlined_under(body, None)
1371    }
1372
1373    fn inlined_under(body: &str, limit: Option<u32>) -> String {
1374        inlined_with(body, limit, true).0
1375    }
1376
1377    /// The same, with the called once half on or off, and with what the step said about it.
1378    fn inlined_with(body: &str, limit: Option<u32>, once: bool) -> (String, String) {
1379        let mut names = Interner::new();
1380        let text = format!("{HEAD}{body}");
1381        let mut module = rucc_ir::parse(&text, &mut names).expect("the fixture parses");
1382        let said = format!("{:?}", run(&mut module, limit, once, Isa::baseline()));
1383        if let Err(errors) = rucc_ir::verify(&module, &names) {
1384            panic!("the inliner left invalid IR, {errors:?}\n{}", rucc_ir::print(&module, &names));
1385        }
1386        (rucc_ir::print(&module, &names), said)
1387    }
1388
1389    /// A callee built for SSE4.2 stays a call from a caller that is not, whether it asked to be
1390    /// inlined always or only hinted, and goes in where the caller is built for it too.
1391    #[test]
1392    fn a_callee_built_for_more_than_its_caller_stays_a_call() {
1393        let sse42 = "mmx,sse,sse2,sse3,ssse3,sse4.1,sse4.2,popcnt,crc32,fxsr";
1394        for attrs in ["always_inline", "inline_hint"] {
1395            let body = format!(
1396                r#"
1397func @step(i32) -> i32, linkage(internal), attrs({attrs}), target "{sse42}" {{
1398block0(%0: i32):
1399    %1 = add.i32 %0, %0
1400    return %1
1401}}
1402
1403func @plain(i32) -> i32, linkage(external) {{
1404block0(%0: i32):
1405    %1 = call @step(%0) : (i32) -> i32
1406    return %1
1407}}
1408
1409func @fast(i32) -> i32, linkage(external), target "{sse42}" {{
1410block0(%0: i32):
1411    %1 = call @step(%0) : (i32) -> i32
1412    return %1
1413}}
1414"#
1415            );
1416            let (out, said) = inlined_with(&body, Some(100), false);
1417            let plain = &out
1418                [out.find("func @plain").expect("plain")..out.find("func @fast").expect("fast")];
1419            let fast = &out[out.find("func @fast").expect("fast")..];
1420            assert!(plain.contains("call @step"), "{attrs}: {out}");
1421            assert!(!fast.contains("call @step"), "{attrs}: {out}");
1422            assert!(said.contains("not inlined: target specific option mismatch"), "{said}");
1423        }
1424    }
1425
1426    /// The body goes where the call was, its return becomes a jump, and its local goes to the
1427    /// caller's entry block.
1428    #[test]
1429    fn a_call_to_an_always_inline_function_is_replaced_by_its_body() {
1430        let out = inlined(
1431            r#"
1432func @twice(i32) -> i32, linkage(linkonce), attrs(always_inline) {
1433block0(%0: i32):
1434    %1 = alloca, size 4, align 4
1435    %2 = add.i32 %0, %0
1436    return %2
1437}
1438
1439func @g(i32) -> i32, linkage(external) {
1440block0(%0: i32):
1441    %1 = call @twice(%0) : (i32) -> i32
1442    %2 = add.i32 %1, %1
1443    return %2
1444}
1445"#,
1446        );
1447        let g = &out[out.find("func @g").expect("g is there")..];
1448        assert!(!g.contains("call @twice"), "{out}");
1449        assert!(g.contains("alloca"), "{out}");
1450    }
1451
1452    /// A `static` one that every call was inlined into goes, at every level, and one that a call
1453    /// still reaches, or that `used` keeps, stays.
1454    #[test]
1455    fn a_static_always_inline_function_is_not_kept_once_every_call_is_inlined() {
1456        let body = r#"
1457func @twice(i32) -> i32, linkage(internal), attrs(always_inline) {
1458block0(%0: i32):
1459    %1 = mul.i32 %0, %0
1460    return %1
1461}
1462
1463func @g(i32) -> i32, linkage(external) {
1464block0(%0: i32):
1465    %1 = call @twice(%0) : (i32) -> i32
1466    return %1
1467}
1468"#;
1469        let out = inlined(body);
1470        assert_eq!(out.matches("mul ").count(), 1, "{out}");
1471        assert!(!out.contains("linkage(internal)"), "{out}");
1472        let kept = inlined(&body.replace("attrs(always_inline)", "attrs(always_inline, used)"));
1473        assert_eq!(kept.matches("mul ").count(), 2, "{kept}");
1474    }
1475
1476    /// An `i` operand the caller passed as arithmetic over constants is the constant once the call
1477    /// is inlined, at `-O0` too, the way `_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC` has to be.
1478    #[test]
1479    fn an_asm_operand_passed_as_constant_arithmetic_is_the_constant_once_inlined() {
1480        let out = inlined(
1481            r#"
1482func @round(i32), linkage(internal), attrs(always_inline) {
1483block0(%0: i32):
1484    inline_asm.volatile "roundps %0, %%xmm0, %%xmm0", "i", "xmm0"(%0)
1485    return
1486}
1487
1488func @g(), linkage(external) {
1489block0:
1490    %0 = iconst.i32 8
1491    %1 = iconst.i32 1
1492    %2 = or.i32 %0, %1
1493    call @round(%2) : (i32)
1494    return
1495}
1496"#,
1497        );
1498        assert!(out.contains("iconst.i32 9"), "{out}");
1499        assert!(!out.contains("or."), "{out}");
1500    }
1501
1502    /// Gives the instructions of a function those spans, in the order they are laid out.
1503    fn respan(func: &mut Func, spans: &[Span]) {
1504        let insts: Vec<Inst> = func.blocks().flat_map(|block| func.insts(block)).collect();
1505        assert_eq!(insts.len(), spans.len(), "one span for each instruction");
1506        let mut forward = HashMap::new();
1507        for (inst, &span) in insts.into_iter().zip(spans) {
1508            let data = func[inst];
1509            let types: Vec<Type> = data.results().map(|value| func[value].ty).collect();
1510            let new = func.create_inst(data, &types, span);
1511            let old: Vec<Value> = func[inst].results().collect();
1512            forward.extend(old.into_iter().zip(func[new].results().collect::<Vec<Value>>()));
1513            func.insert_before(new, inst);
1514            func.remove_inst(inst);
1515        }
1516        crate::uses::substitute(func, &forward);
1517    }
1518
1519    /// The callee's slot and the store of its parameter are at its opening brace, which is outside
1520    /// the caller. Once inlined, the slot says what the caller's own slots say and the store says
1521    /// the call, while a statement of the callee keeps its place.
1522    #[test]
1523    fn an_inlined_prologue_names_no_line_outside_the_caller() {
1524        let text = format!(
1525            r#"{HEAD}
1526func @twice(i32) -> i32, linkage(internal), attrs(always_inline) {{
1527block0(%0: i32):
1528    %1 = alloca, size 4, align 4
1529    store %0 -> %1, align 4
1530    %2 = load.i32 %1, align 4
1531    %3 = add.i32 %2, %2
1532    return %3
1533}}
1534
1535func @g(i32) -> i32, linkage(external) {{
1536block0(%0: i32):
1537    %1 = alloca, size 4, align 4
1538    %2 = call @twice(%0) : (i32) -> i32
1539    return %2
1540}}
1541"#
1542        );
1543        let mut names = Interner::new();
1544        let mut module = rucc_ir::parse(&text, &mut names).expect("the fixture parses");
1545        let (brace, statement) = (Span::new(10, 50), Span::new(20, 30));
1546        let (frame, call) = (Span::new(60, 100), Span::new(70, 80));
1547        let g = names.intern("g");
1548        for id in module.funcs().collect::<Vec<FuncId>>() {
1549            let func = &mut module[id];
1550            if func.name == g {
1551                func.declared = frame;
1552                respan(func, &[frame, call, call]);
1553            } else {
1554                func.declared = brace;
1555                respan(func, &[brace, brace, statement, statement, statement]);
1556            }
1557        }
1558        run(&mut module, None, true, Isa::baseline());
1559        let id = module.funcs().find(|&id| module[id].name == g).expect("g is there");
1560        let func = &module[id];
1561        let mut seen = Vec::new();
1562        for block in func.blocks() {
1563            for inst in func.insts(block) {
1564                let span = func.span(inst);
1565                assert_ne!(span, brace, "{:?} kept the callee's brace", func[inst].opcode);
1566                seen.push((func[inst].opcode, span));
1567            }
1568        }
1569        assert!(seen.iter().all(|&(opcode, span)| opcode != Opcode::Alloca || span == frame));
1570        assert!(seen.contains(&(Opcode::Store, call)), "{seen:?}");
1571        assert!(seen.contains(&(Opcode::Load, statement)), "{seen:?}");
1572    }
1573
1574    /// The anonymous arguments of the outer call are what the pack stands for, and the out of line
1575    /// copy that still has one is a declaration afterwards.
1576    #[test]
1577    fn a_pack_is_the_anonymous_arguments_of_the_call_inlined() {
1578        let out = inlined(
1579            r#"
1580func @inner(i32, ...) -> i32, linkage(external);
1581
1582func @wrap(i32, ...) -> i32, linkage(linkonce), attrs(always_inline) {
1583block0(%0: i32):
1584    %1 = va_arg_pack.i32
1585    %2 = call @inner(%0, %1) : (i32, ...) -> i32
1586    return %2
1587}
1588
1589func @g(i64, f64) -> i32, linkage(external) {
1590block0(%0: i64, %1: f64):
1591    %2 = iconst.i32 7
1592    %3 = call @wrap(%2, %0, %1) : (i32, ...) -> i32
1593    return %3
1594}
1595"#,
1596        );
1597        assert!(
1598            out.contains("func @wrap(i32, ...) -> i32, linkage(external), attrs(always_inline);"),
1599            "{out}"
1600        );
1601        assert!(!out.contains("va_arg_pack"), "{out}");
1602        assert!(out.contains("call @inner(%"), "{out}");
1603    }
1604
1605    /// The length is how many anonymous arguments the call had.
1606    #[test]
1607    fn a_pack_length_is_the_count_of_the_anonymous_arguments() {
1608        let out = inlined(
1609            r#"
1610func @wrap(i32, ...) -> i32, linkage(linkonce), attrs(always_inline) {
1611block0(%0: i32):
1612    %1 = va_arg_pack_len.i32
1613    return %1
1614}
1615
1616func @g(i64, f64) -> i32, linkage(external) {
1617block0(%0: i64, %1: f64):
1618    %2 = iconst.i32 7
1619    %3 = call @wrap(%2, %0, %1) : (i32, ...) -> i32
1620    return %3
1621}
1622"#,
1623        );
1624        let g = &out[out.find("func @g").expect("g is there")..];
1625        assert!(g.contains("iconst.i32 2"), "{out}");
1626        assert!(!g.contains("call @wrap"), "{out}");
1627    }
1628
1629    /// A function declared `inline`, which is a call left alone at `-O0` and inlined above it.
1630    const HINTED: &str = r#"
1631func @bump(i32) -> i32, linkage(external), attrs(inline_hint) {
1632block0(%0: i32):
1633    %1 = iconst.i32 1
1634    %2 = add.i32 %0, %1
1635    return %2
1636}
1637
1638func @g(i32) -> i32, linkage(external) {
1639block0(%0: i32):
1640    %1 = call @bump(%0) : (i32) -> i32
1641    return %1
1642}
1643"#;
1644
1645    /// A small function declared `inline` goes in when there is a limit and stays a call when
1646    /// there is none, which is `-O0`.
1647    #[test]
1648    fn a_small_function_declared_inline_is_inlined_above_o0() {
1649        let out = inlined_under(HINTED, Some(70));
1650        let g = &out[out.find("func @g").expect("g is there")..];
1651        assert!(!g.contains("call @bump"), "{out}");
1652        let out = inlined_under(HINTED, None);
1653        assert!(out.contains("call @bump"), "{out}");
1654    }
1655
1656    /// One that is larger than the limit stays a call.
1657    #[test]
1658    fn a_function_declared_inline_over_the_limit_is_left_alone() {
1659        let out = inlined_under(HINTED, Some(2));
1660        assert!(out.contains("call @bump"), "{out}");
1661    }
1662
1663    /// A `static` function nobody declared `inline`, called from one place.
1664    const ONCE: &str = r#"
1665func @scale(i32) -> i32, linkage(internal) {
1666block0(%0: i32):
1667    %1 = iconst.i32 3
1668    %2 = mul.i32 %0, %1
1669    %3 = iconst.i32 1
1670    %4 = add.i32 %2, %3
1671    return %4
1672}
1673
1674func @g(i32) -> i32, linkage(external) {
1675block0(%0: i32):
1676    %1 = call @scale(%0) : (i32) -> i32
1677    return %1
1678}
1679"#;
1680
1681    /// Its one call goes in above `-O0` whatever the limit on a function declared `inline` is,
1682    /// since the out of line copy goes away with it, and stays a call at `-O0`.
1683    #[test]
1684    fn a_static_function_called_once_is_inlined_above_o0() {
1685        let out = inlined_under(ONCE, Some(2));
1686        let g = &out[out.find("func @g").expect("g is there")..];
1687        assert!(!g.contains("call @scale"), "{out}");
1688        assert!(g.contains("mul %0"), "{out}");
1689        let out = inlined_under(ONCE, None);
1690        assert!(out.contains("call @scale"), "{out}");
1691    }
1692
1693    /// Once its one call is inlined nothing reaches the body, so it goes rather than being emitted
1694    /// next to the copy.
1695    #[test]
1696    fn a_static_function_called_once_is_not_kept_once_inlined() {
1697        let out = inlined_under(ONCE, Some(2));
1698        assert_eq!(out.matches("mul ").count(), 1, "{out}");
1699        assert!(!out.contains("linkage(internal)"), "{out}");
1700    }
1701
1702    /// Called from two places it is a function nobody declared `inline`, which stays a call.
1703    #[test]
1704    fn a_static_function_called_twice_stays_a_call() {
1705        let twice = ONCE.replace(
1706            "    %1 = call @scale(%0) : (i32) -> i32\n    return %1",
1707            "    %1 = call @scale(%0) : (i32) -> i32\n    %2 = call @scale(%1) : (i32) -> i32\n    \
1708             return %2",
1709        );
1710        assert_ne!(twice, ONCE);
1711        let out = inlined_under(&twice, Some(70));
1712        assert_eq!(out.matches("call @scale").count(), 2, "{out}");
1713    }
1714
1715    /// One another object can call keeps its copy, so inlining the call here would only grow the
1716    /// program.
1717    #[test]
1718    fn a_function_other_objects_can_call_stays_a_call_when_called_once() {
1719        let external = ONCE.replace(
1720            "@scale(i32) -> i32, linkage(internal)",
1721            "@scale(i32) -> i32, linkage(external)",
1722        );
1723        assert_ne!(external, ONCE);
1724        let out = inlined_under(&external, Some(70));
1725        assert!(out.contains("call @scale"), "{out}");
1726    }
1727
1728    /// Its address is a way to reach it that is not the call, so the copy has to stay and the call
1729    /// stays with it.
1730    #[test]
1731    fn a_static_function_whose_address_is_taken_stays_a_call() {
1732        let taken = ONCE.replace(
1733            "    %1 = call @scale(%0) : (i32) -> i32\n    return %1",
1734            "    %1 = call @scale(%0) : (i32) -> i32\n    %2 = global_addr @scale\n    return %1",
1735        );
1736        assert_ne!(taken, ONCE);
1737        let out = inlined_under(&taken, Some(70));
1738        assert!(out.contains("call @scale"), "{out}");
1739    }
1740
1741    /// `ONCE` with the one call `depth` loops deep in `g`. Each header enters the next loop in or
1742    /// goes back round the one around it, and the block with the call goes back round the
1743    /// innermost, so the call is inside every one of them.
1744    fn nested(depth: usize) -> String {
1745        let scale = &ONCE[..ONCE.find("func @g").expect("g is there")];
1746        let (body, out) = (depth + 1, depth + 2);
1747        let mut g = String::from(
1748            "func @g(i32, i1) -> i32, linkage(external) {\nblock0(%0: i32, %1: i1):\n    jump block1\n",
1749        );
1750        for header in 1..=depth {
1751            let back = if header == 1 { out } else { header - 1 };
1752            g += &format!("block{header}:\n    br_if %1, block{}, block{back}\n", header + 1);
1753        }
1754        g += &format!(
1755            "block{body}:\n    %2 = call @scale(%0) : (i32) -> i32\n    jump block{depth}\n"
1756        );
1757        g += &format!("block{out}:\n    return %0\n}}\n");
1758        format!("{scale}{g}")
1759    }
1760
1761    /// gcc's `max-inline-functions-called-once-loop-depth` is 6, so a call six loops deep is
1762    /// inlined and one seven deep is not, with a remark that says why.
1763    #[test]
1764    fn a_static_function_called_once_is_inlined_no_more_than_six_loops_deep() {
1765        let (out, said) = inlined_with(&nested(6), Some(70), true);
1766        assert!(!out.contains("call @scale"), "{out}");
1767        assert!(!said.contains("too many loops"), "{said}");
1768        let (out, said) = inlined_with(&nested(7), Some(70), true);
1769        assert!(out.contains("call @scale"), "{out}");
1770        assert!(said.contains("call inside too many loops"), "{said}");
1771    }
1772
1773    /// `-fno-inline-functions-called-once` keeps the call that would otherwise go, and leaves the
1774    /// function declared `inline` to the limit that governs it.
1775    #[test]
1776    fn the_called_once_half_can_be_turned_off_alone() {
1777        let (out, _) = inlined_with(ONCE, Some(70), false);
1778        assert!(out.contains("call @scale"), "{out}");
1779        let hinted = ONCE.replace("linkage(internal) {", "linkage(internal), attrs(inline_hint) {");
1780        assert_ne!(hinted, ONCE);
1781        let (out, _) = inlined_with(&hinted, Some(70), false);
1782        assert!(!out.contains("call @scale"), "{out}");
1783    }
1784
1785    /// `noinline` is kept whoever calls it how often.
1786    #[test]
1787    fn a_static_function_called_once_and_marked_noinline_stays_a_call() {
1788        let kept = ONCE.replace("linkage(internal) {", "linkage(internal), attrs(noinline) {");
1789        assert_ne!(kept, ONCE);
1790        let out = inlined_under(&kept, Some(70));
1791        assert!(out.contains("call @scale"), "{out}");
1792    }
1793
1794    /// Each copy of a body that takes the address of its own label gets a label of its own, which
1795    /// is `990208-1.c`.
1796    #[test]
1797    fn each_copy_of_a_label_address_is_a_label_of_its_own() {
1798        let out = inlined_under(
1799            r#"
1800func @here() -> ptr, linkage(internal), attrs(inline_hint) {
1801block0:
1802    jump block1
1803block1:
1804    %0 = block_addr block1
1805    return %0
1806}
1807
1808func @g() -> i1, linkage(external) {
1809block0:
1810    %0 = call @here() : () -> ptr
1811    %1 = call @here() : () -> ptr
1812    %2 = icmp eq %0, %1
1813    return %2
1814}
1815"#,
1816            Some(70),
1817        );
1818        let g = &out[out.find("func @g").expect("g is there")..];
1819        assert!(!g.contains("call @here"), "{out}");
1820        assert_eq!(g.matches("block_addr").count(), 2, "{out}");
1821    }
1822
1823    /// A body that jumps through a label address is refused, since a table of them may be what
1824    /// it jumps through and the table names the original body.
1825    #[test]
1826    fn a_computed_goto_is_not_inlined() {
1827        let out = inlined_under(
1828            r#"
1829func @jump(ptr) -> i32, linkage(internal), attrs(inline_hint) {
1830block0(%0: ptr):
1831    indirect_br %0, block1
1832block1:
1833    %1 = iconst.i32 1
1834    return %1
1835}
1836
1837func @g(ptr) -> i32, linkage(external) {
1838block0(%0: ptr):
1839    %1 = call @jump(%0) : (ptr) -> i32
1840    return %1
1841}
1842"#,
1843            Some(70),
1844        );
1845        assert!(out.contains("call @jump"), "{out}");
1846    }
1847
1848    /// A function that reaches itself is left as a call rather than unrolled for ever.
1849    #[test]
1850    fn a_recursive_always_inline_function_is_left_alone() {
1851        let out = inlined(
1852            r#"
1853func @r(i32) -> i32, linkage(linkonce), attrs(always_inline) {
1854block0(%0: i32):
1855    %1 = call @r(%0) : (i32) -> i32
1856    return %1
1857}
1858"#,
1859        );
1860        assert!(out.contains("call @r("), "{out}");
1861    }
1862
1863    /// Two general purpose registers of a structure that the second call has one left for, which
1864    /// goes to memory instead.
1865    #[test]
1866    fn a_structure_that_would_straddle_the_registers_goes_to_memory() {
1867        let int = (Type::int(64), Abi::Plain);
1868        let outer = [int];
1869        let before = [int, int, int, int, int];
1870        let forwarded = [int, int];
1871        let sysv = |before: &[(Type, Abi)], groups| {
1872            forwardable(Convention::SysV, &outer, before, &forwarded, groups)
1873        };
1874        assert_eq!(sysv(&before, Some(&[2])), Some(vec![0]));
1875        assert_eq!(sysv(&before, Some(&[1, 1])), Some(Vec::new()));
1876        assert_eq!(sysv(&before, None), None);
1877        assert_eq!(sysv(&outer, None), Some(Vec::new()));
1878    }
1879
1880    /// A function with a `cleanup` handler of its own, called from inside the scope of one of the
1881    /// caller's, both under `-fexceptions`. The lowering gives each call in a handler's scope an
1882    /// `unwound` and a branch on it to a pad that runs the handlers and resumes the unwind.
1883    const PADDED: &str = r#"
1884func @hinted(i32), linkage(internal), attrs(inline_hint) {
1885block0(%0: i32):
1886    %1 = alloca, size 4, align 4
1887    %2 = iconst.i32 5
1888    store %2 -> %1, align 4
1889    call @leave(%0) : (i32)
1890    %3 = unwound.i1
1891    br_if %3, block1, block2
1892
1893block1:
1894    %4 = landing.ptr
1895    call @done(%1) : (ptr)
1896    call @_Unwind_Resume(%4) : (ptr)
1897    unreachable
1898
1899block2:
1900    call @done(%1) : (ptr)
1901    return
1902}
1903
1904func @outer(i32), linkage(external) {
1905block0(%0: i32):
1906    %1 = alloca, size 4, align 4
1907    %2 = iconst.i32 1
1908    store %2 -> %1, align 4
1909    call @hinted(%0) : (i32)
1910    %3 = unwound.i1
1911    br_if %3, block1, block2
1912
1913block1:
1914    %4 = landing.ptr
1915    call @done(%1) : (ptr)
1916    call @_Unwind_Resume(%4) : (ptr)
1917    unreachable
1918
1919block2:
1920    call @done(%1) : (ptr)
1921    return
1922}
1923"#;
1924
1925    /// Inlined, the body's calls are calls the caller's pad has to cover, since an unwind out of
1926    /// any of them passes through the call that was there. The one with a pad of its own keeps it,
1927    /// and that pad's `_Unwind_Resume` is covered like the rest, which is how the unwind gets from
1928    /// the callee's handler to the caller's. The `unwound` of the call that went has nothing left
1929    /// to ask about and goes with it.
1930    #[test]
1931    fn a_call_with_a_landing_pad_is_inlined_and_the_pad_covers_the_body() {
1932        let out = inlined_under(PADDED, Some(70));
1933        let outer = &out[out.find("func @outer").expect("outer is there")..];
1934        assert!(!outer.contains("call @hinted"), "{out}");
1935        // The callee's own edge, and one for each of the three calls in the body that had none:
1936        // the handler on the way out, the handler in its pad and the resume after it.
1937        assert_eq!(outer.matches("= unwound").count(), 4, "{outer}");
1938        assert_eq!(outer.matches("= landing").count(), 2, "{outer}");
1939        assert_eq!(outer.matches("call @_Unwind_Resume").count(), 2, "{outer}");
1940        // The caller's pad is where three of those edges go, and the fourth is the callee's.
1941        assert_eq!(outer.matches(", block1, ").count(), 3, "{outer}");
1942        // At -O0 nothing is inlined, pad or not.
1943        assert!(inlined(PADDED).contains("call @hinted"));
1944    }
1945}