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//! From `-O1` up the same splice takes a call to a function declared `inline` whose body, once its
52//! own calls are settled, is no larger than `max-inline-insns-single`, the limit gcc gives such a
53//! callee. It is the declared half of gcc's early inliner and not the rest of it: a function
54//! nobody declared `inline` is left alone however small it is, and nothing here weighs the call
55//! against the growth the way section 33.4 wants the later inliner to. What it is for is the code
56//! after it. A `__builtin_constant_p` in the body of such a function asks about a parameter, and
57//! only once the body is where the call was can the answer be the constant the caller passed,
58//! which is what gcc answers and what `bcp-1.c` checks. `-fno-inline` turns this half off and
59//! leaves `always_inline` alone, which is what the flag does in gcc.
60//!
61//! A body that takes the address of one of its own labels is copied with the label, so each copy
62//! has an address of its own, which is what gcc does and what `990208-1.c` checks. A body that
63//! jumps to such an address, or whose labels a static table holds, is refused, since the copy
64//! would still be reaching into the original.
65
66use std::collections::{HashMap, HashSet};
67
68use rucc_base::Symbol;
69use rucc_ir::{
70    Abi, AsmInfo, AttrSet, Block, BlockCall, BlockCallList, CallInfo, Def, Drains, Extra, Float,
71    Func, FuncId, Imm, Inst, InstData, Linkage, MemInfo, MemOrder, Module, Opcode, Restrict,
72    Signature, SwitchInfo, Type, VaInfo, Value, ValueList,
73};
74use rucc_tuple::{Arch, Os};
75
76use crate::Stats;
77
78/// What the step calls itself in a remark, and the name `-fno-inline` turns the declared half off
79/// by.
80pub const NAME: &str = "inline";
81
82const INLINED: &str = "always_inline call inlined";
83
84const HINT_INLINED: &str = "inline call inlined";
85
86/// Which of the two reasons a function is inlined for.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88enum Kind {
89    /// `always_inline`, which is a promise.
90    Always,
91    /// `inline`, which is a hint taken when the body is small enough.
92    Hinted,
93}
94
95/// Why a call to an `always_inline` function was not inlined.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum InlineFailure {
98    /// The function reaches itself through calls of this kind.
99    Recursive,
100    /// The arguments or the results of the call are not what the body takes and gives.
101    Mismatch,
102    /// A parameter is a structure passed by value, whose copy the call is what makes.
103    ByValue,
104    /// The body starts a variable argument list of its own, which only a frame of its own has.
105    VaStart,
106    /// The body jumps to a label by its address, or a static table holds one of its labels,
107    /// either of which would still name the original body from the copy.
108    ComputedGoto,
109    /// The body calls `setjmp`, whose frame would become the caller's.
110    Setjmp,
111    /// The body saves the registers it was called with, which in the caller hold the caller's.
112    ApplyArgs,
113    /// The IR has memory SSA in it, which this step runs before.
114    MemorySsa,
115    /// A `va_arg_pack` whose arguments cannot be forwarded to where it is.
116    Pack,
117    /// The body grows the stack by an amount only known when it runs, which in a loop in the
118    /// caller would grow it once for every time round. Only a hint is refused for this.
119    Alloca,
120    /// The body is larger than a callee declared `inline` is allowed to be.
121    TooLarge,
122}
123
124impl InlineFailure {
125    /// What `-fopt-info` says about it.
126    #[must_use]
127    pub const fn why(self) -> &'static str {
128        match self {
129            Self::Recursive => "always_inline call not inlined: recursive",
130            Self::Mismatch => "always_inline call not inlined: arguments do not match",
131            Self::ByValue => "always_inline call not inlined: structure passed by value",
132            Self::VaStart => "always_inline call not inlined: callee uses va_start",
133            Self::ComputedGoto => "always_inline call not inlined: callee has a computed goto",
134            Self::Setjmp => "always_inline call not inlined: callee calls setjmp",
135            Self::ApplyArgs => "always_inline call not inlined: callee uses __builtin_apply_args",
136            Self::MemorySsa => "always_inline call not inlined: memory SSA present",
137            Self::Pack => "always_inline call not inlined: va_arg_pack cannot be forwarded",
138            Self::Alloca => "always_inline call not inlined: callee calls alloca",
139            Self::TooLarge => "always_inline call not inlined: callee too large",
140        }
141    }
142
143    /// What `-fopt-info` says about it for a call to a function that was only declared `inline`.
144    #[must_use]
145    pub const fn hint(self) -> &'static str {
146        match self {
147            Self::Recursive => "inline call not inlined: recursive",
148            Self::Mismatch => "inline call not inlined: arguments do not match",
149            Self::ByValue => "inline call not inlined: structure passed by value",
150            Self::VaStart => "inline call not inlined: callee uses va_start",
151            Self::ComputedGoto => "inline call not inlined: callee has a computed goto",
152            Self::Setjmp => "inline call not inlined: callee calls setjmp",
153            Self::ApplyArgs => "inline call not inlined: callee uses __builtin_apply_args",
154            Self::MemorySsa => "inline call not inlined: memory SSA present",
155            Self::Pack => "inline call not inlined: va_arg_pack cannot be forwarded",
156            Self::Alloca => "inline call not inlined: callee calls alloca",
157            Self::TooLarge => "inline call not inlined: callee too large",
158        }
159    }
160}
161
162/// Inlines every call to an `always_inline` function that can be, and with a `limit` every call to
163/// a function declared `inline` whose body is no larger than that, and says what it did where.
164///
165/// Then turns every function still holding a `va_arg_pack` into a declaration. See the module
166/// documentation for why that is the right thing to do with one.
167pub fn run(module: &mut Module, limit: Option<u32>) -> Vec<(FuncId, Stats)> {
168    let wanted: HashMap<Symbol, (FuncId, Kind)> = module
169        .funcs()
170        .filter(|&id| !module[id].is_declaration())
171        .filter_map(|id| {
172            let set = module[id].attrs.set;
173            let kind = if set.contains(AttrSet::ALWAYS_INLINE) {
174                Kind::Always
175            } else if limit.is_some()
176                && set.contains(AttrSet::INLINE_HINT)
177                && set.without(AttrSet::NOINLINE | AttrSet::OPTNONE | AttrSet::NAKED) == set
178            {
179                Kind::Hinted
180            } else {
181                return None;
182            };
183            Some((module[id].name, (id, kind)))
184        })
185        .collect();
186    let mut done = Vec::new();
187    if !wanted.is_empty() {
188        let convention = Convention::of(module);
189        let mut state = HashMap::new();
190        let limit = limit.map_or(0, |limit| usize::try_from(limit).unwrap_or(usize::MAX));
191        let how = How { wanted: &wanted, convention, limit };
192        for id in module.funcs().collect::<Vec<FuncId>>() {
193            settle(module, id, &how, &mut state, &mut done);
194        }
195    }
196    withdraw(module);
197    done
198}
199
200/// What stays the same for every function [`settle`] visits.
201struct How<'a> {
202    /// The functions whose calls are inlined, by name, and why.
203    wanted: &'a HashMap<Symbol, (FuncId, Kind)>,
204    /// The calling convention the pack is forwarded under.
205    convention: Convention,
206    /// How many instructions a callee declared `inline` may have.
207    limit: usize,
208}
209
210/// Where a function is in being settled.
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212enum State {
213    /// Its calls are being inlined, so a call back to it from one of them is a cycle.
214    Settling,
215    /// Every call of this kind in it that can be inlined has been.
216    Settled,
217}
218
219/// Inlines the `always_inline` calls in one function, settling each callee first.
220fn settle(
221    module: &mut Module,
222    id: FuncId,
223    how: &How<'_>,
224    state: &mut HashMap<FuncId, State>,
225    done: &mut Vec<(FuncId, Stats)>,
226) {
227    if state.contains_key(&id) || module[id].is_declaration() {
228        return;
229    }
230    state.insert(id, State::Settling);
231    // The calls as the function was written. A call that arrives inside a body being inlined is
232    // one the callee's own settling already had its chance at. A function that asked not to be
233    // optimized is left with its calls, except for the ones that are a promise.
234    let optnone = module[id].attrs.set.contains(AttrSet::OPTNONE);
235    let calls: Vec<(Inst, FuncId, Kind)> = {
236        let func = &module[id];
237        func.blocks()
238            .flat_map(|block| func.insts(block))
239            .filter_map(|inst| {
240                let Extra::Call(info) = func[inst].extra else { return None };
241                if func[inst].opcode != Opcode::Call {
242                    return None;
243                }
244                let callee = func[info].callee?;
245                let &(callee, kind) = how.wanted.get(&callee)?;
246                (kind == Kind::Always || !optnone).then_some((inst, callee, kind))
247            })
248            .collect()
249    };
250    let mut stats = Stats::new();
251    for (call, callee, kind) in calls {
252        let why = |failure: InlineFailure| match kind {
253            Kind::Always => failure.why(),
254            Kind::Hinted => failure.hint(),
255        };
256        if callee == id || state.get(&callee) == Some(&State::Settling) {
257            stats.missed(why(InlineFailure::Recursive));
258            continue;
259        }
260        settle(module, callee, how, state, done);
261        // Measured once the callee is settled, since what is copied is the body with its own
262        // calls already inlined.
263        if kind == Kind::Hinted && size(&module[callee]) > how.limit {
264            stats.missed(why(InlineFailure::TooLarge));
265            continue;
266        }
267        match splice(module, id, call, callee, how.convention, kind) {
268            Ok(()) if kind == Kind::Always => stats.optimized(INLINED),
269            Ok(()) => stats.optimized(HINT_INLINED),
270            Err(failure) => stats.missed(why(failure)),
271        }
272    }
273    state.insert(id, State::Settled);
274    if !stats.is_empty() {
275        done.push((id, stats));
276    }
277}
278
279/// How many instructions a body has, which is what the limit on a callee declared `inline` counts.
280fn size(func: &Func) -> usize {
281    func.blocks().map(|block| func.insts(block).count()).sum()
282}
283
284/// Inlines one call, or says why not and leaves the caller as it was.
285fn splice(
286    module: &mut Module,
287    caller: FuncId,
288    call: Inst,
289    callee: FuncId,
290    convention: Convention,
291    kind: Kind,
292) -> Result<(), InlineFailure> {
293    // Out of the module for the length of the splice, so that the callee can be read while the
294    // caller is written. The two are different functions, since a call to itself is refused
295    // before this.
296    let stand_in = Func::new(module[caller].name, Signature::new());
297    let mut func = std::mem::replace(&mut module[caller], stand_in);
298    let result = check(&func, call, &module[callee], convention, kind)
299        .map(|plan| copy(&mut func, call, &module[callee], &plan));
300    module[caller] = func;
301    result
302}
303
304/// What [`check`] found out that [`copy`] needs.
305struct Plan {
306    /// How many of the call's arguments go to the callee's entry block.
307    fixed: usize,
308    /// The rest of them, which are what a `va_arg_pack` stands for.
309    extras: Vec<Value>,
310    /// How each of those travels, one for each.
311    abis: Vec<Abi>,
312    /// How many of them each C argument became, where the lowering said.
313    groups: Option<Vec<u32>>,
314    /// For each call in the callee that passes the pack on, the groups that have to go to memory
315    /// because the registers they went in are taken there, which is only ever under SysV.
316    spills: HashMap<Inst, Vec<usize>>,
317}
318
319/// Whether one call can be inlined, and what the splice needs to know if it can.
320fn check(
321    func: &Func,
322    call: Inst,
323    callee: &Func,
324    convention: Convention,
325    kind: Kind,
326) -> Result<Plan, InlineFailure> {
327    let entry = callee.entry().ok_or(InlineFailure::Mismatch)?;
328    let params = &callee[entry].params;
329    let args = &func[func[call].args];
330    let Extra::Call(info) = func[call].extra else { return Err(InlineFailure::Mismatch) };
331    let signature = &func[func[info].signature];
332    if args.len() < params.len()
333        || (args.len() > params.len() && !callee.signature().variadic)
334        || args.iter().zip(params).any(|(&arg, &param)| func[arg].ty != callee[param].ty)
335    {
336        return Err(InlineFailure::Mismatch);
337    }
338    let returns: Vec<Type> = callee.signature().return_types().collect();
339    let results: Vec<Type> = func[call].results().map(|value| func[value].ty).collect();
340    if results.len() > returns.len() || results.iter().zip(&returns).any(|(a, b)| a != b) {
341        return Err(InlineFailure::Mismatch);
342    }
343    if callee.signature().params.iter().any(|param| matches!(param.abi, Abi::ByVal { .. })) {
344        return Err(InlineFailure::ByValue);
345    }
346
347    let fixed = params.len();
348    let extras = args[fixed..].to_vec();
349    let abis = expand(&func[func[info].varargs], extras.len());
350    let groups = func.arg_groups(call).and_then(|groups| past(groups, fixed));
351    let mut plan = Plan { fixed, extras, abis, groups, spills: HashMap::new() };
352    let outer: Vec<(Type, Abi)> = args[..fixed]
353        .iter()
354        .enumerate()
355        .map(|(at, &arg)| (func[arg].ty, signature.params.get(at).map_or(Abi::Plain, |p| p.abi)))
356        .collect();
357
358    if callee.named_blocks().next().is_some() {
359        return Err(InlineFailure::ComputedGoto);
360    }
361    let mut packs = HashSet::new();
362    let mut counted = false;
363    for block in callee.blocks() {
364        for inst in callee.insts(block) {
365            match callee[inst].opcode {
366                Opcode::VaStart => return Err(InlineFailure::VaStart),
367                Opcode::IndirectBr => return Err(InlineFailure::ComputedGoto),
368                Opcode::Alloca if kind == Kind::Hinted && !callee[inst].args.is_empty() => {
369                    return Err(InlineFailure::Alloca);
370                }
371                Opcode::SetjmpMarker => return Err(InlineFailure::Setjmp),
372                Opcode::ApplyArgs => return Err(InlineFailure::ApplyArgs),
373                Opcode::MemEntry => return Err(InlineFailure::MemorySsa),
374                Opcode::VaArgPack => packs.extend(callee[inst].results()),
375                Opcode::VaArgPackLen => counted = true,
376                _ => {}
377            }
378        }
379    }
380    // A pack standing for another pack is the caller being an inline definition itself, and
381    // what that pack stands for, or how many it is, is not known until the caller is inlined
382    // somewhere.
383    if (counted || !packs.is_empty()) && plan.extras.iter().any(|&value| is_pack(func, value)) {
384        return Err(InlineFailure::Pack);
385    }
386    if packs.is_empty() {
387        return Ok(plan);
388    }
389    for block in callee.blocks() {
390        for inst in callee.insts(block) {
391            let data = &callee[inst];
392            let used = callee[data.args].iter().position(|value| packs.contains(value));
393            let passed = callee
394                .successors(inst)
395                .any(|to| callee[to.args].iter().any(|value| packs.contains(value)));
396            if passed {
397                return Err(InlineFailure::Pack);
398            }
399            let Some(at) = used else { continue };
400            let args = &callee[data.args];
401            let Extra::Call(inner) = data.extra else { return Err(InlineFailure::Pack) };
402            if at + 1 != args.len() || !matches!(data.opcode, Opcode::Call | Opcode::CallIndirect) {
403                return Err(InlineFailure::Pack);
404            }
405            let skip = usize::from(data.opcode == Opcode::CallIndirect);
406            let named = &callee[callee[inner].signature].params;
407            let written = &args[skip..at];
408            let anonymous = expand(&callee[callee[inner].varargs], written.len() + 1 - named.len());
409            let before: Vec<(Type, Abi)> = written
410                .iter()
411                .enumerate()
412                .map(|(index, &value)| {
413                    let abi = match named.get(index) {
414                        Some(param) => param.abi,
415                        None => anonymous[index - named.len()],
416                    };
417                    (callee[value].ty, abi)
418                })
419                .collect();
420            let forwarded: Vec<(Type, Abi)> = plan
421                .extras
422                .iter()
423                .zip(&plan.abis)
424                .map(|(&value, &abi)| (func[value].ty, abi))
425                .collect();
426            let spills =
427                forwardable(convention, &outer, &before, &forwarded, plan.groups.as_deref())
428                    .ok_or(InlineFailure::Pack)?;
429            if !spills.is_empty() {
430                plan.spills.insert(inst, spills);
431            }
432        }
433    }
434    Ok(plan)
435}
436
437/// Whether a value is what a `va_arg_pack` produced.
438fn is_pack(func: &Func, value: Value) -> bool {
439    matches!(func[value].def, Def::Result { inst, .. } if func[inst].opcode == Opcode::VaArgPack)
440}
441
442/// A list of how the anonymous arguments travel, with the empty one that means every one of them
443/// is plain written out.
444fn expand(abis: &[Abi], count: usize) -> Vec<Abi> {
445    if abis.is_empty() { vec![Abi::Plain; count] } else { abis.to_vec() }
446}
447
448/// The groups past the first `fixed` values, or `None` when a group straddles that point, which
449/// no lowering does.
450fn past(groups: &[u32], fixed: usize) -> Option<Vec<u32>> {
451    let mut seen = 0;
452    let mut rest = groups.iter();
453    while seen < fixed {
454        seen += usize::try_from(*rest.next()?).ok()?;
455    }
456    (seen == fixed).then(|| rest.copied().collect())
457}
458
459/// The calling convention, as far as forwarding arguments from one call to another cares.
460#[derive(Debug, Clone, Copy, PartialEq, Eq)]
461enum Convention {
462    /// x86-64 System V, where a structure goes in registers whole or not at all.
463    SysV,
464    /// Windows x64, where every argument is one slot and nothing depends on what came before.
465    Slots,
466    /// Everything else, where the answer is only trusted when nothing moves.
467    Other,
468}
469
470impl Convention {
471    fn of(module: &Module) -> Self {
472        match (module.tuple.arch(), module.tuple.os()) {
473            (Arch::X86_64, Os::Windows) => Self::Slots,
474            (Arch::X86_64, _) => Self::SysV,
475            _ => Self::Other,
476        }
477    }
478}
479
480/// Where one value goes under SysV, as far as registers are concerned.
481#[derive(Debug, Clone, Copy, PartialEq, Eq)]
482enum Class {
483    /// General purpose registers, this many of them.
484    Gpr(u32),
485    /// One vector register.
486    Sse,
487    /// The argument area, whatever registers are left.
488    Memory,
489}
490
491fn class(ty: Type, abi: Abi) -> Class {
492    if abi.indirect() && !matches!(abi, Abi::Sret { .. }) {
493        Class::Memory
494    } else if ty.is_vector() {
495        Class::Sse
496    } else if ty.is_float() {
497        if ty.format() == Some(Float::F80) { Class::Memory } else { Class::Sse }
498    } else if ty.is_int() && ty.bits() > 64 {
499        Class::Gpr(2)
500    } else {
501        Class::Gpr(1)
502    }
503}
504
505/// The registers a SysV call has used so far.
506#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
507struct Regs {
508    gpr: u32,
509    sse: u32,
510}
511
512impl Regs {
513    const GPR: u32 = 6;
514    const SSE: u32 = 8;
515
516    fn after(values: &[(Type, Abi)]) -> Self {
517        let mut regs = Self::default();
518        for &(ty, abi) in values {
519            regs.take(class(ty, abi));
520        }
521        regs
522    }
523
524    fn fits(self, gpr: u32, sse: u32) -> bool {
525        self.gpr + gpr <= Self::GPR && self.sse + sse <= Self::SSE
526    }
527
528    /// Takes what one value of that class needs, if there is room, and says whether there was.
529    fn take(&mut self, class: Class) -> bool {
530        let (gpr, sse) = match class {
531            Class::Gpr(count) => (count, 0),
532            Class::Sse => (0, 1),
533            Class::Memory => return false,
534        };
535        let room = self.fits(gpr, sse);
536        if room {
537            self.gpr += gpr;
538            self.sse += sse;
539        }
540        room
541    }
542}
543
544/// Whether the anonymous arguments of one call can be passed on to another, and which of them
545/// have to go to memory on the way.
546///
547/// `outer` is what the first call passes to the named parameters, `before` is what the second
548/// passes ahead of the pack, and `forwarded` is what the pack stands for, all as the lowering left
549/// them. The answer is yes when the second call has used the same registers as the first by the
550/// time the pack starts, since then every argument goes where it went. Under SysV it is also yes
551/// when the registers used differ but no argument that decides where it goes as a whole would
552/// decide differently, which is a small structure in memory that would now fit. A structure in
553/// registers that no longer fits goes to memory, as it would have if the second call had been
554/// written out, and the answer says which groups those are. `groups` is what says which values
555/// are one structure, and without it only the first answer is given.
556fn forwardable(
557    convention: Convention,
558    outer: &[(Type, Abi)],
559    before: &[(Type, Abi)],
560    forwarded: &[(Type, Abi)],
561    groups: Option<&[u32]>,
562) -> Option<Vec<usize>> {
563    match convention {
564        Convention::Slots => Some(Vec::new()),
565        Convention::Other => {
566            let count = |values: &[(Type, Abi)]| {
567                let mut ints = 0;
568                let mut floats = 0;
569                for &(ty, abi) in values {
570                    if abi.indirect() {
571                        return None;
572                    }
573                    if ty.is_float() || ty.is_vector() { floats += 1 } else { ints += 1 }
574                }
575                Some((ints, floats))
576            };
577            (count(outer).is_some() && count(outer) == count(before)).then(Vec::new)
578        }
579        Convention::SysV => {
580            let mut first = Regs::after(outer);
581            let mut second = Regs::after(before);
582            if first == second {
583                return Some(Vec::new());
584            }
585            let mut spills = Vec::new();
586            let mut at = 0;
587            for (index, &count) in groups?.iter().enumerate() {
588                let group = usize::try_from(count).ok().and_then(|n| forwarded.get(at..at + n))?;
589                at += group.len();
590                match *group {
591                    [] => {}
592                    // One value, which goes wherever the registers left send it in either call,
593                    // except for a small structure in memory. That may be there because it did
594                    // not fit, and if the second call has more room it would be in registers.
595                    [(ty, abi)] => {
596                        if let Abi::ByVal { size, .. } = abi {
597                            let small = size <= 16; // not a threshold: the SysV register limit
598                            if small && (second.gpr < first.gpr || second.sse < first.sse) {
599                                return None;
600                            }
601                            continue;
602                        }
603                        first.take(class(ty, abi));
604                        second.take(class(ty, abi));
605                    }
606                    // A structure in registers, which the first call found room for. It goes in
607                    // the second one's registers if there is room, and to memory if not.
608                    _ => {
609                        let mut gpr = 0;
610                        let mut sse = 0;
611                        for &(ty, abi) in group {
612                            match class(ty, abi) {
613                                Class::Gpr(count) => gpr += count,
614                                Class::Sse => sse += 1,
615                                Class::Memory => return None,
616                            }
617                        }
618                        if !first.fits(gpr, sse) {
619                            return None;
620                        }
621                        first.gpr += gpr;
622                        first.sse += sse;
623                        if second.fits(gpr, sse) {
624                            second.gpr += gpr;
625                            second.sse += sse;
626                        } else {
627                            spills.push(index);
628                        }
629                    }
630                }
631            }
632            (at == forwarded.len()).then_some(spills)
633        }
634    }
635}
636
637/// Splices the callee in where the call is, which [`check`] has said it can be.
638fn copy(func: &mut Func, call: Inst, callee: &Func, plan: &Plan) {
639    let block = func.block_of(call).expect("a call being inlined is in a block");
640    let entry = func.entry().expect("a function with a call in it has a body");
641
642    // The part after the call, which takes the call's results as parameters.
643    let after = func.create_block();
644    let mut forward = HashMap::new();
645    for result in func[call].results().collect::<Vec<Value>>() {
646        let ty = func[result].ty;
647        forward.insert(result, func.append_param(after, ty));
648    }
649    let moving: Vec<Inst> = func.insts(block).skip_while(|&inst| inst != call).skip(1).collect();
650    for inst in moving {
651        func.remove_inst(inst);
652        func.append_inst(after, inst);
653    }
654
655    // The callee's blocks and their parameters, and then its instructions with their results, so
656    // that every value exists before any operand is written.
657    //
658    // The entry block's parameters are the call's arguments themselves rather than parameters of
659    // the copy, since nothing branches to an entry block and so nothing else arrives there. That
660    // way a constant argument is a constant in the body straight away, and the folding that runs
661    // next sees `1 + 1` rather than a block parameter that only `simplify-cfg` would later find
662    // is always `1`.
663    let start = callee.entry().expect("checked to have a body");
664    let passed = func[func[call].args][..plan.fixed].to_vec();
665    let mut blocks = HashMap::new();
666    let mut values = HashMap::new();
667    for from in callee.blocks() {
668        let to = func.create_block();
669        if from == start {
670            values.extend(callee[from].params.iter().copied().zip(passed.iter().copied()));
671        } else {
672            for &param in &callee[from].params {
673                values.insert(param, func.append_param(to, callee[param].ty));
674            }
675        }
676        blocks.insert(from, to);
677    }
678    let mut made = Vec::new();
679    for from in callee.blocks() {
680        for inst in callee.insts(from) {
681            let data = &callee[inst];
682            if data.opcode == Opcode::VaArgPack {
683                continue;
684            }
685            let opcode = match data.opcode {
686                Opcode::Return => Opcode::Jump,
687                Opcode::VaArgPackLen => Opcode::IConst,
688                opcode => opcode,
689            };
690            let types: Vec<Type> = data.results().map(|value| callee[value].ty).collect();
691            let shell = InstData { flags: data.flags, ..InstData::new(opcode) };
692            let new = func.create_inst(shell, &types, callee.span(inst));
693            for (old, value) in data.results().zip(func[new].results().collect::<Vec<Value>>()) {
694                values.insert(old, value);
695            }
696            if opcode == Opcode::Alloca && data.args.is_empty() {
697                let first = func.insts(entry).next().expect("an entry block ends in something");
698                func.insert_before(new, first);
699            } else {
700                func.append_inst(blocks[&from], new);
701            }
702            made.push((inst, new));
703        }
704    }
705
706    let keep = func[call].results().count();
707    for (inst, new) in made {
708        let data = &callee[inst];
709        let mut args: Vec<Value> = callee[data.args]
710            .iter()
711            .filter(|value| values.contains_key(value))
712            .map(|value| values[value])
713            .collect();
714        let packed = args.len() != data.args.len();
715        let extra = if data.opcode == Opcode::Return {
716            args.truncate(keep);
717            let to = func.push_values(&args);
718            args.clear();
719            Extra::Targets(func.push_block_calls(&[BlockCall::new(after, to)]))
720        } else if data.opcode == Opcode::VaArgPackLen {
721            // How many C arguments the pack stands for, which is the groups where the lowering
722            // said and one value each where it did not.
723            let count = plan.groups.as_ref().map_or(plan.extras.len(), Vec::len);
724            let count = i128::try_from(count).expect("fewer arguments than that");
725            Extra::Imm(func.add_imm(Imm::int(count, Type::int(32))))
726        } else {
727            match data.extra {
728                Extra::Imm(imm) => Extra::Imm(func.add_imm(callee[imm])),
729                Extra::Mem(mem) => Extra::Mem(func.add_mem(unscoped(callee[mem]))),
730                Extra::Rmw(op, mem) => Extra::Rmw(op, func.add_mem(unscoped(callee[mem]))),
731                Extra::Targets(list) => {
732                    Extra::Targets(targets(func, callee, list, &blocks, &values))
733                }
734                Extra::Call(info) => {
735                    let info = callee[info];
736                    let mut forwarded = None;
737                    let signature = callee[info.signature].clone();
738                    let mut abis = callee[info.varargs].to_vec();
739                    if packed {
740                        let skip = usize::from(data.opcode == Opcode::CallIndirect);
741                        let written = args.len() - skip - signature.params.len();
742                        abis = expand(&abis, written + 1);
743                        abis.truncate(written);
744                        let spills = plan.spills.get(&inst).map_or(&[][..], Vec::as_slice);
745                        forwarded = pass_on(func, entry, new, spills, plan, &mut args, &mut abis);
746                        if abis.iter().all(|&abi| abi == Abi::Plain) {
747                            abis.clear();
748                        }
749                    }
750                    let signature = func.add_signature(signature);
751                    let varargs = func.push_abis(&abis);
752                    if let Some(groups) = callee.arg_groups(inst) {
753                        let mut groups = groups.to_vec();
754                        let known = if packed {
755                            groups.pop();
756                            forwarded.as_ref().map(|outer| groups.extend_from_slice(outer))
757                        } else {
758                            Some(())
759                        };
760                        if known.is_some() {
761                            func.set_arg_groups(new, groups);
762                        }
763                    }
764                    Extra::Call(func.add_call(CallInfo { callee: info.callee, signature, varargs }))
765                }
766                Extra::Switch(info) => {
767                    let info = callee[info];
768                    let cases = func.push_imms(&callee[info.cases]);
769                    let targets = targets(func, callee, info.targets, &blocks, &values);
770                    Extra::Switch(func.add_switch(SwitchInfo { targets, cases }))
771                }
772                Extra::Asm(info) => {
773                    let info = callee[info];
774                    let targets = targets(func, callee, info.targets, &blocks, &values);
775                    Extra::Asm(func.add_asm(AsmInfo { targets, ..info }))
776                }
777                Extra::VaObject(info) => {
778                    let info = callee[info];
779                    let mem = func.add_mem(unscoped(callee[info.mem]));
780                    let slots = func.push_slots(&callee[info.slots]);
781                    Extra::VaObject(func.add_va_object(VaInfo { mem, slots }))
782                }
783                other => other,
784            }
785        };
786        func[new].args = if args.is_empty() { ValueList::EMPTY } else { func.push_values(&args) };
787        func[new].extra = extra;
788    }
789
790    // And the call itself, which becomes a jump to the copy of the entry block.
791    let to = ValueList::EMPTY;
792    let targets = func.push_block_calls(&[BlockCall::new(blocks[&start], to)]);
793    let span = func.span(call);
794    let jump = func.create_inst(
795        InstData { extra: Extra::Targets(targets), ..InstData::new(Opcode::Jump) },
796        &[],
797        span,
798    );
799    crate::uses::substitute(func, &forward);
800    func.remove_inst(call);
801    func.append_inst(block, jump);
802}
803
804/// Appends what the pack stands for to the arguments of one call, putting each group the plan
805/// says has to go to memory in a slot of the caller's that the call copies from, and gives back
806/// the groups as they are after that.
807fn pass_on(
808    func: &mut Func,
809    entry: Block,
810    call: Inst,
811    spills: &[usize],
812    plan: &Plan,
813    args: &mut Vec<Value>,
814    abis: &mut Vec<Abi>,
815) -> Option<Vec<u32>> {
816    let Some(groups) = plan.groups.as_deref().filter(|_| !spills.is_empty()) else {
817        args.extend_from_slice(&plan.extras);
818        abis.extend_from_slice(&plan.abis);
819        return plan.groups.clone();
820    };
821    let mut now = Vec::with_capacity(groups.len());
822    let mut at = 0;
823    for (index, &count) in groups.iter().enumerate() {
824        let end = at + count as usize;
825        if spills.contains(&index) {
826            let (slot, size) = spill(func, entry, call, &plan.extras[at..end]);
827            args.push(slot);
828            abis.push(Abi::ByVal { size, align: 8, drains: Drains::Nothing });
829            now.push(1);
830        } else {
831            args.extend_from_slice(&plan.extras[at..end]);
832            abis.extend_from_slice(&plan.abis[at..end]);
833            now.push(count);
834        }
835        at = end;
836    }
837    Some(now)
838}
839
840/// Stores the pieces of one structure, eight bytes apart the way the registers held them, in a
841/// new slot at the top of the caller, just ahead of the call, and gives back the slot and its size.
842fn spill(func: &mut Func, entry: Block, call: Inst, pieces: &[Value]) -> (Value, u64) {
843    let span = func.span(call);
844    let bytes = |ty: Type| {
845        if ty == Type::PTR { 8 } else { u64::from(ty.bits() * ty.lanes()).div_ceil(8) }
846    };
847    let size: u64 = pieces.iter().map(|&piece| bytes(func[piece].ty).next_multiple_of(8)).sum();
848    let info = MemInfo {
849        size,
850        align: 8,
851        order: MemOrder::NotAtomic,
852        tbaa: None,
853        owns: 0,
854        restrict: Restrict::NONE,
855    };
856    let mem = func.add_mem(info);
857    let alloca = InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) };
858    let alloca = func.create_inst(alloca, &[Type::PTR], span);
859    let first = func.insts(entry).next().expect("an entry block ends in something");
860    func.insert_before(alloca, first);
861    let slot = func[alloca].results().next().expect("an alloca has a result");
862
863    let mut offset = 0;
864    for &piece in pieces {
865        let ty = func[piece].ty;
866        let width = bytes(ty);
867        let mut address = slot;
868        if offset != 0 {
869            let imm = func.add_imm(Imm::int(i128::from(offset), Type::int(64)));
870            let amount = InstData { extra: Extra::Imm(imm), ..InstData::new(Opcode::IConst) };
871            let amount = func.create_inst(amount, &[Type::int(64)], span);
872            func.insert_before(amount, call);
873            let amount = func[amount].results().next().expect("a constant has a result");
874            let add = InstData {
875                args: func.push_values(&[slot, amount]),
876                ..InstData::new(Opcode::PtrAdd)
877            };
878            let add = func.create_inst(add, &[Type::PTR], span);
879            func.insert_before(add, call);
880            address = func[add].results().next().expect("an address has a result");
881        }
882        let info = MemInfo { size: width, ..info };
883        let store = InstData {
884            args: func.push_values(&[piece, address]),
885            extra: Extra::Mem(func.add_mem(info)),
886            ..InstData::new(Opcode::Store)
887        };
888        let store = func.create_inst(store, &[], span);
889        func.insert_before(store, call);
890        offset += width.next_multiple_of(8);
891    }
892    (slot, size)
893}
894
895/// An access as the callee described it, less the `restrict` scope, whose numbers are the
896/// callee's and could mean a different scope in the caller.
897fn unscoped(info: MemInfo) -> MemInfo {
898    MemInfo { restrict: Restrict::NONE, ..info }
899}
900
901/// A list of branch targets copied across, with the blocks and the arguments mapped.
902fn targets(
903    func: &mut Func,
904    callee: &Func,
905    list: BlockCallList,
906    blocks: &HashMap<Block, Block>,
907    values: &HashMap<Value, Value>,
908) -> BlockCallList {
909    let calls: Vec<BlockCall> = callee[list]
910        .iter()
911        .map(|call| {
912            let args: Vec<Value> = callee[call.args].iter().map(|value| values[value]).collect();
913            let args = func.push_values(&args);
914            BlockCall { block: blocks[&call.block], args, hint: call.hint }
915        })
916        .collect();
917    func.push_block_calls(&calls)
918}
919
920/// Turns every function that still holds a `va_arg_pack` or a `va_arg_pack_len`, and every one
921/// marked `inline_only`, into a declaration of the same name.
922fn withdraw(module: &mut Module) {
923    for id in module.funcs().collect::<Vec<FuncId>>() {
924        let func = &module[id];
925        if func.is_declaration() {
926            continue;
927        }
928        let holds = func
929            .blocks()
930            .flat_map(|block| func.insts(block))
931            .any(|inst| matches!(func[inst].opcode, Opcode::VaArgPack | Opcode::VaArgPackLen));
932        if !holds && !func.attrs.set.contains(AttrSet::INLINE_ONLY) {
933            continue;
934        }
935        let mut declared = Func::new(func.name, func.signature().clone());
936        declared.spelled = func.spelled;
937        declared.visibility = func.visibility;
938        declared.attrs = func.attrs;
939        declared.attrs.set = declared.attrs.set.without(AttrSet::INLINE_ONLY);
940        declared.declared = func.declared;
941        declared.linkage = Linkage::External;
942        module[id] = declared;
943    }
944}
945
946#[cfg(test)]
947mod tests {
948    use rucc_base::Interner;
949
950    use super::*;
951
952    const HEAD: &str = r#"; ModuleID = 't.c'
953; format 0
954target triple = "x86_64-unknown-linux-gnu"
955target datalayout = "e-p:64:64-i64:64-f80:128-S128"
956"#;
957
958    fn inlined(body: &str) -> String {
959        inlined_under(body, None)
960    }
961
962    fn inlined_under(body: &str, limit: Option<u32>) -> String {
963        let mut names = Interner::new();
964        let text = format!("{HEAD}{body}");
965        let mut module = rucc_ir::parse(&text, &mut names).expect("the fixture parses");
966        run(&mut module, limit);
967        if let Err(errors) = rucc_ir::verify(&module, &names) {
968            panic!("the inliner left invalid IR, {errors:?}\n{}", rucc_ir::print(&module, &names));
969        }
970        rucc_ir::print(&module, &names)
971    }
972
973    /// The body goes where the call was, its return becomes a jump, and its local goes to the
974    /// caller's entry block.
975    #[test]
976    fn a_call_to_an_always_inline_function_is_replaced_by_its_body() {
977        let out = inlined(
978            r#"
979func @twice(i32) -> i32, linkage(linkonce), attrs(always_inline) {
980block0(%0: i32):
981    %1 = alloca, size 4, align 4
982    %2 = add.i32 %0, %0
983    return %2
984}
985
986func @g(i32) -> i32, linkage(external) {
987block0(%0: i32):
988    %1 = call @twice(%0) : (i32) -> i32
989    %2 = add.i32 %1, %1
990    return %2
991}
992"#,
993        );
994        let g = &out[out.find("func @g").expect("g is there")..];
995        assert!(!g.contains("call @twice"), "{out}");
996        assert!(g.contains("alloca"), "{out}");
997    }
998
999    /// The anonymous arguments of the outer call are what the pack stands for, and the out of line
1000    /// copy that still has one is a declaration afterwards.
1001    #[test]
1002    fn a_pack_is_the_anonymous_arguments_of_the_call_inlined() {
1003        let out = inlined(
1004            r#"
1005func @inner(i32, ...) -> i32, linkage(external);
1006
1007func @wrap(i32, ...) -> i32, linkage(linkonce), attrs(always_inline) {
1008block0(%0: i32):
1009    %1 = va_arg_pack.i32
1010    %2 = call @inner(%0, %1) : (i32, ...) -> i32
1011    return %2
1012}
1013
1014func @g(i64, f64) -> i32, linkage(external) {
1015block0(%0: i64, %1: f64):
1016    %2 = iconst.i32 7
1017    %3 = call @wrap(%2, %0, %1) : (i32, ...) -> i32
1018    return %3
1019}
1020"#,
1021        );
1022        assert!(
1023            out.contains("func @wrap(i32, ...) -> i32, linkage(external), attrs(always_inline);"),
1024            "{out}"
1025        );
1026        assert!(!out.contains("va_arg_pack"), "{out}");
1027        assert!(out.contains("call @inner(%"), "{out}");
1028    }
1029
1030    /// The length is how many anonymous arguments the call had.
1031    #[test]
1032    fn a_pack_length_is_the_count_of_the_anonymous_arguments() {
1033        let out = inlined(
1034            r#"
1035func @wrap(i32, ...) -> i32, linkage(linkonce), attrs(always_inline) {
1036block0(%0: i32):
1037    %1 = va_arg_pack_len.i32
1038    return %1
1039}
1040
1041func @g(i64, f64) -> i32, linkage(external) {
1042block0(%0: i64, %1: f64):
1043    %2 = iconst.i32 7
1044    %3 = call @wrap(%2, %0, %1) : (i32, ...) -> i32
1045    return %3
1046}
1047"#,
1048        );
1049        let g = &out[out.find("func @g").expect("g is there")..];
1050        assert!(g.contains("iconst.i32 2"), "{out}");
1051        assert!(!g.contains("call @wrap"), "{out}");
1052    }
1053
1054    /// A function declared `inline`, which is a call left alone at `-O0` and inlined above it.
1055    const HINTED: &str = r#"
1056func @bump(i32) -> i32, linkage(external), attrs(inline_hint) {
1057block0(%0: i32):
1058    %1 = iconst.i32 1
1059    %2 = add.i32 %0, %1
1060    return %2
1061}
1062
1063func @g(i32) -> i32, linkage(external) {
1064block0(%0: i32):
1065    %1 = call @bump(%0) : (i32) -> i32
1066    return %1
1067}
1068"#;
1069
1070    /// A small function declared `inline` goes in when there is a limit and stays a call when
1071    /// there is none, which is `-O0`.
1072    #[test]
1073    fn a_small_function_declared_inline_is_inlined_above_o0() {
1074        let out = inlined_under(HINTED, Some(70));
1075        let g = &out[out.find("func @g").expect("g is there")..];
1076        assert!(!g.contains("call @bump"), "{out}");
1077        let out = inlined_under(HINTED, None);
1078        assert!(out.contains("call @bump"), "{out}");
1079    }
1080
1081    /// One that is larger than the limit stays a call.
1082    #[test]
1083    fn a_function_declared_inline_over_the_limit_is_left_alone() {
1084        let out = inlined_under(HINTED, Some(2));
1085        assert!(out.contains("call @bump"), "{out}");
1086    }
1087
1088    /// Each copy of a body that takes the address of its own label gets a label of its own, which
1089    /// is `990208-1.c`.
1090    #[test]
1091    fn each_copy_of_a_label_address_is_a_label_of_its_own() {
1092        let out = inlined_under(
1093            r#"
1094func @here() -> ptr, linkage(internal), attrs(inline_hint) {
1095block0:
1096    jump block1
1097block1:
1098    %0 = block_addr block1
1099    return %0
1100}
1101
1102func @g() -> i1, linkage(external) {
1103block0:
1104    %0 = call @here() : () -> ptr
1105    %1 = call @here() : () -> ptr
1106    %2 = icmp eq %0, %1
1107    return %2
1108}
1109"#,
1110            Some(70),
1111        );
1112        let g = &out[out.find("func @g").expect("g is there")..];
1113        assert!(!g.contains("call @here"), "{out}");
1114        assert_eq!(g.matches("block_addr").count(), 2, "{out}");
1115    }
1116
1117    /// A body that jumps through a label address is refused, since a table of them may be what
1118    /// it jumps through and the table names the original body.
1119    #[test]
1120    fn a_computed_goto_is_not_inlined() {
1121        let out = inlined_under(
1122            r#"
1123func @jump(ptr) -> i32, linkage(internal), attrs(inline_hint) {
1124block0(%0: ptr):
1125    indirect_br %0, block1
1126block1:
1127    %1 = iconst.i32 1
1128    return %1
1129}
1130
1131func @g(ptr) -> i32, linkage(external) {
1132block0(%0: ptr):
1133    %1 = call @jump(%0) : (ptr) -> i32
1134    return %1
1135}
1136"#,
1137            Some(70),
1138        );
1139        assert!(out.contains("call @jump"), "{out}");
1140    }
1141
1142    /// A function that reaches itself is left as a call rather than unrolled for ever.
1143    #[test]
1144    fn a_recursive_always_inline_function_is_left_alone() {
1145        let out = inlined(
1146            r#"
1147func @r(i32) -> i32, linkage(linkonce), attrs(always_inline) {
1148block0(%0: i32):
1149    %1 = call @r(%0) : (i32) -> i32
1150    return %1
1151}
1152"#,
1153        );
1154        assert!(out.contains("call @r("), "{out}");
1155    }
1156
1157    /// Two general purpose registers of a structure that the second call has one left for, which
1158    /// goes to memory instead.
1159    #[test]
1160    fn a_structure_that_would_straddle_the_registers_goes_to_memory() {
1161        let int = (Type::int(64), Abi::Plain);
1162        let outer = [int];
1163        let before = [int, int, int, int, int];
1164        let forwarded = [int, int];
1165        let sysv = |before: &[(Type, Abi)], groups| {
1166            forwardable(Convention::SysV, &outer, before, &forwarded, groups)
1167        };
1168        assert_eq!(sysv(&before, Some(&[2])), Some(vec![0]));
1169        assert_eq!(sysv(&before, Some(&[1, 1])), Some(Vec::new()));
1170        assert_eq!(sysv(&before, None), None);
1171        assert_eq!(sysv(&outer, None), Some(Vec::new()));
1172    }
1173}