1use 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
103pub const NAME: &str = "inline";
106
107pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119enum Kind {
120 Always,
122 Hinted,
124 Once,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum InlineFailure {
132 Recursive,
134 Mismatch,
136 ByValue,
140 VaStart,
142 ComputedGoto,
145 Setjmp,
147 ApplyArgs,
149 MemorySsa,
151 Pack,
153 Alloca,
156 TooLarge,
158 TooDeep,
160 Unwinds,
164 Target,
167}
168
169impl InlineFailure {
170 #[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 #[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 #[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
246pub 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
314fn 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
352fn 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
366fn 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
402struct How<'a> {
404 wanted: &'a HashMap<Symbol, (FuncId, Kind)>,
406 convention: Convention,
408 limit: usize,
410 isa: Isa,
412}
413
414#[derive(Debug, Clone, Copy, PartialEq, Eq)]
416enum State {
417 Settling,
419 Settled,
421}
422
423fn 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 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 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 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 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
522fn size(func: &Func) -> usize {
524 func.blocks().map(|block| func.insts(block).count()).sum()
525}
526
527fn splice(
529 module: &mut Module,
530 caller: FuncId,
531 call: Inst,
532 callee: FuncId,
533 convention: Convention,
534 kind: Kind,
535) -> Result<(), InlineFailure> {
536 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
547struct Plan {
549 fixed: usize,
551 extras: Vec<Value>,
553 abis: Vec<Abi>,
555 groups: Option<Vec<u32>>,
557 spills: HashMap<Inst, Vec<usize>>,
560}
561
562fn check(
564 func: &Func,
565 call: Inst,
566 callee: &Func,
567 convention: Convention,
568 kind: Kind,
569) -> Result<Plan, InlineFailure> {
570 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, ¶m)| 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 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 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
692fn is_pack(func: &Func, value: Value) -> bool {
694 matches!(func[value].def, Def::Result { inst, .. } if func[inst].opcode == Opcode::VaArgPack)
695}
696
697fn expand(abis: &[Abi], count: usize) -> Vec<Abi> {
700 if abis.is_empty() { vec![Abi::Plain; count] } else { abis.to_vec() }
701}
702
703fn 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
716enum Convention {
717 SysV,
719 Slots,
721 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
737enum Class {
738 Gpr(u32),
740 Sse,
742 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#[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 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
799fn 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 [(ty, abi)] => {
851 if let Abi::ByVal { size, .. } = abi {
852 let small = size <= 16; 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 _ => {
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
892fn 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 let arms = unwind_arms(func, call);
900
901 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 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 let start = callee.entry().expect("checked to have a body");
939 let mut passed = func[func[call].args][..plan.fixed].to_vec();
940 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 ¶m in &callee[from].params {
955 values.insert(param, func.append_param(to, callee[param].ty));
956 }
957 }
958 blocks.insert(from, to);
959 }
960 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 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 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 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 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 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
1155fn 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
1173fn 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
1209fn 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
1244fn 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
1299fn unscoped(info: MemInfo) -> MemInfo {
1302 MemInfo { restrict: Restrict::NONE, ..info }
1303}
1304
1305fn 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
1324fn 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
1343fn 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 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 #[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 #[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 #[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 #[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 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 #[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 #[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 #[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 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 #[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 #[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 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 #[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 #[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 #[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 #[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 #[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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 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 assert_eq!(outer.matches(", block1, ").count(), 3, "{outer}");
1942 assert!(inlined(PADDED).contains("call @hinted"));
1944 }
1945}