1use std::collections::{HashMap, HashSet};
64
65use rucc_base::Symbol;
66use rucc_ir::{
67 Abi, AsmInfo, AttrSet, Block, BlockCall, BlockCallList, CallInfo, Def, Drains, Extra, Float,
68 Func, FuncId, Imm, Inst, InstData, Linkage, MemInfo, MemOrder, Module, Opcode, Restrict,
69 Signature, SwitchInfo, Type, VaInfo, Value, ValueList,
70};
71use rucc_tuple::{Arch, Os};
72
73use crate::Stats;
74
75pub const NAME: &str = "inline";
78
79const INLINED: &str = "always_inline call inlined";
80
81const HINT_INLINED: &str = "inline call inlined";
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85enum Kind {
86 Always,
88 Hinted,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum InlineFailure {
95 Recursive,
97 Mismatch,
99 ByValue,
101 VaStart,
103 ComputedGoto,
106 Setjmp,
108 ApplyArgs,
110 MemorySsa,
112 Pack,
114 Alloca,
117 TooLarge,
119}
120
121impl InlineFailure {
122 #[must_use]
124 pub const fn why(self) -> &'static str {
125 match self {
126 Self::Recursive => "always_inline call not inlined: recursive",
127 Self::Mismatch => "always_inline call not inlined: arguments do not match",
128 Self::ByValue => "always_inline call not inlined: structure passed by value",
129 Self::VaStart => "always_inline call not inlined: callee uses va_start",
130 Self::ComputedGoto => "always_inline call not inlined: callee has a computed goto",
131 Self::Setjmp => "always_inline call not inlined: callee calls setjmp",
132 Self::ApplyArgs => "always_inline call not inlined: callee uses __builtin_apply_args",
133 Self::MemorySsa => "always_inline call not inlined: memory SSA present",
134 Self::Pack => "always_inline call not inlined: va_arg_pack cannot be forwarded",
135 Self::Alloca => "always_inline call not inlined: callee calls alloca",
136 Self::TooLarge => "always_inline call not inlined: callee too large",
137 }
138 }
139
140 #[must_use]
142 pub const fn hint(self) -> &'static str {
143 match self {
144 Self::Recursive => "inline call not inlined: recursive",
145 Self::Mismatch => "inline call not inlined: arguments do not match",
146 Self::ByValue => "inline call not inlined: structure passed by value",
147 Self::VaStart => "inline call not inlined: callee uses va_start",
148 Self::ComputedGoto => "inline call not inlined: callee has a computed goto",
149 Self::Setjmp => "inline call not inlined: callee calls setjmp",
150 Self::ApplyArgs => "inline call not inlined: callee uses __builtin_apply_args",
151 Self::MemorySsa => "inline call not inlined: memory SSA present",
152 Self::Pack => "inline call not inlined: va_arg_pack cannot be forwarded",
153 Self::Alloca => "inline call not inlined: callee calls alloca",
154 Self::TooLarge => "inline call not inlined: callee too large",
155 }
156 }
157}
158
159pub fn run(module: &mut Module, limit: Option<u32>) -> Vec<(FuncId, Stats)> {
165 let wanted: HashMap<Symbol, (FuncId, Kind)> = module
166 .funcs()
167 .filter(|&id| !module[id].is_declaration())
168 .filter_map(|id| {
169 let set = module[id].attrs.set;
170 let kind = if set.contains(AttrSet::ALWAYS_INLINE) {
171 Kind::Always
172 } else if limit.is_some()
173 && set.contains(AttrSet::INLINE_HINT)
174 && set.without(AttrSet::NOINLINE | AttrSet::OPTNONE | AttrSet::NAKED) == set
175 {
176 Kind::Hinted
177 } else {
178 return None;
179 };
180 Some((module[id].name, (id, kind)))
181 })
182 .collect();
183 let mut done = Vec::new();
184 if !wanted.is_empty() {
185 let convention = Convention::of(module);
186 let mut state = HashMap::new();
187 let limit = limit.map_or(0, |limit| usize::try_from(limit).unwrap_or(usize::MAX));
188 let how = How { wanted: &wanted, convention, limit };
189 for id in module.funcs().collect::<Vec<FuncId>>() {
190 settle(module, id, &how, &mut state, &mut done);
191 }
192 }
193 withdraw(module);
194 done
195}
196
197struct How<'a> {
199 wanted: &'a HashMap<Symbol, (FuncId, Kind)>,
201 convention: Convention,
203 limit: usize,
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209enum State {
210 Settling,
212 Settled,
214}
215
216fn settle(
218 module: &mut Module,
219 id: FuncId,
220 how: &How<'_>,
221 state: &mut HashMap<FuncId, State>,
222 done: &mut Vec<(FuncId, Stats)>,
223) {
224 if state.contains_key(&id) || module[id].is_declaration() {
225 return;
226 }
227 state.insert(id, State::Settling);
228 let optnone = module[id].attrs.set.contains(AttrSet::OPTNONE);
232 let calls: Vec<(Inst, FuncId, Kind)> = {
233 let func = &module[id];
234 func.blocks()
235 .flat_map(|block| func.insts(block))
236 .filter_map(|inst| {
237 let Extra::Call(info) = func[inst].extra else { return None };
238 if func[inst].opcode != Opcode::Call {
239 return None;
240 }
241 let callee = func[info].callee?;
242 let &(callee, kind) = how.wanted.get(&callee)?;
243 (kind == Kind::Always || !optnone).then_some((inst, callee, kind))
244 })
245 .collect()
246 };
247 let mut stats = Stats::new();
248 for (call, callee, kind) in calls {
249 let why = |failure: InlineFailure| match kind {
250 Kind::Always => failure.why(),
251 Kind::Hinted => failure.hint(),
252 };
253 if callee == id || state.get(&callee) == Some(&State::Settling) {
254 stats.missed(why(InlineFailure::Recursive));
255 continue;
256 }
257 settle(module, callee, how, state, done);
258 if kind == Kind::Hinted && size(&module[callee]) > how.limit {
261 stats.missed(why(InlineFailure::TooLarge));
262 continue;
263 }
264 match splice(module, id, call, callee, how.convention, kind) {
265 Ok(()) if kind == Kind::Always => stats.optimized(INLINED),
266 Ok(()) => stats.optimized(HINT_INLINED),
267 Err(failure) => stats.missed(why(failure)),
268 }
269 }
270 state.insert(id, State::Settled);
271 if !stats.is_empty() {
272 done.push((id, stats));
273 }
274}
275
276fn size(func: &Func) -> usize {
278 func.blocks().map(|block| func.insts(block).count()).sum()
279}
280
281fn splice(
283 module: &mut Module,
284 caller: FuncId,
285 call: Inst,
286 callee: FuncId,
287 convention: Convention,
288 kind: Kind,
289) -> Result<(), InlineFailure> {
290 let stand_in = Func::new(module[caller].name, Signature::new());
294 let mut func = std::mem::replace(&mut module[caller], stand_in);
295 let result = check(&func, call, &module[callee], convention, kind)
296 .map(|plan| copy(&mut func, call, &module[callee], &plan));
297 module[caller] = func;
298 result
299}
300
301struct Plan {
303 fixed: usize,
305 extras: Vec<Value>,
307 abis: Vec<Abi>,
309 groups: Option<Vec<u32>>,
311 spills: HashMap<Inst, Vec<usize>>,
314}
315
316fn check(
318 func: &Func,
319 call: Inst,
320 callee: &Func,
321 convention: Convention,
322 kind: Kind,
323) -> Result<Plan, InlineFailure> {
324 let entry = callee.entry().ok_or(InlineFailure::Mismatch)?;
325 let params = &callee[entry].params;
326 let args = &func[func[call].args];
327 let Extra::Call(info) = func[call].extra else { return Err(InlineFailure::Mismatch) };
328 let signature = &func[func[info].signature];
329 if args.len() < params.len()
330 || (args.len() > params.len() && !callee.signature().variadic)
331 || args.iter().zip(params).any(|(&arg, ¶m)| func[arg].ty != callee[param].ty)
332 {
333 return Err(InlineFailure::Mismatch);
334 }
335 let returns: Vec<Type> = callee.signature().return_types().collect();
336 let results: Vec<Type> = func[call].results().map(|value| func[value].ty).collect();
337 if results.len() > returns.len() || results.iter().zip(&returns).any(|(a, b)| a != b) {
338 return Err(InlineFailure::Mismatch);
339 }
340 if callee.signature().params.iter().any(|param| matches!(param.abi, Abi::ByVal { .. })) {
341 return Err(InlineFailure::ByValue);
342 }
343
344 let fixed = params.len();
345 let extras = args[fixed..].to_vec();
346 let abis = expand(&func[func[info].varargs], extras.len());
347 let groups = func.arg_groups(call).and_then(|groups| past(groups, fixed));
348 let mut plan = Plan { fixed, extras, abis, groups, spills: HashMap::new() };
349 let outer: Vec<(Type, Abi)> = args[..fixed]
350 .iter()
351 .enumerate()
352 .map(|(at, &arg)| (func[arg].ty, signature.params.get(at).map_or(Abi::Plain, |p| p.abi)))
353 .collect();
354
355 if callee.named_blocks().next().is_some() {
356 return Err(InlineFailure::ComputedGoto);
357 }
358 let mut packs = HashSet::new();
359 let mut counted = false;
360 for block in callee.blocks() {
361 for inst in callee.insts(block) {
362 match callee[inst].opcode {
363 Opcode::VaStart => return Err(InlineFailure::VaStart),
364 Opcode::IndirectBr => return Err(InlineFailure::ComputedGoto),
365 Opcode::Alloca if kind == Kind::Hinted && !callee[inst].args.is_empty() => {
366 return Err(InlineFailure::Alloca);
367 }
368 Opcode::SetjmpMarker => return Err(InlineFailure::Setjmp),
369 Opcode::ApplyArgs => return Err(InlineFailure::ApplyArgs),
370 Opcode::MemEntry => return Err(InlineFailure::MemorySsa),
371 Opcode::VaArgPack => packs.extend(callee[inst].results()),
372 Opcode::VaArgPackLen => counted = true,
373 _ => {}
374 }
375 }
376 }
377 if (counted || !packs.is_empty()) && plan.extras.iter().any(|&value| is_pack(func, value)) {
381 return Err(InlineFailure::Pack);
382 }
383 if packs.is_empty() {
384 return Ok(plan);
385 }
386 for block in callee.blocks() {
387 for inst in callee.insts(block) {
388 let data = &callee[inst];
389 let used = callee[data.args].iter().position(|value| packs.contains(value));
390 let passed = callee
391 .successors(inst)
392 .any(|to| callee[to.args].iter().any(|value| packs.contains(value)));
393 if passed {
394 return Err(InlineFailure::Pack);
395 }
396 let Some(at) = used else { continue };
397 let args = &callee[data.args];
398 let Extra::Call(inner) = data.extra else { return Err(InlineFailure::Pack) };
399 if at + 1 != args.len() || !matches!(data.opcode, Opcode::Call | Opcode::CallIndirect) {
400 return Err(InlineFailure::Pack);
401 }
402 let skip = usize::from(data.opcode == Opcode::CallIndirect);
403 let named = &callee[callee[inner].signature].params;
404 let written = &args[skip..at];
405 let anonymous = expand(&callee[callee[inner].varargs], written.len() + 1 - named.len());
406 let before: Vec<(Type, Abi)> = written
407 .iter()
408 .enumerate()
409 .map(|(index, &value)| {
410 let abi = match named.get(index) {
411 Some(param) => param.abi,
412 None => anonymous[index - named.len()],
413 };
414 (callee[value].ty, abi)
415 })
416 .collect();
417 let forwarded: Vec<(Type, Abi)> = plan
418 .extras
419 .iter()
420 .zip(&plan.abis)
421 .map(|(&value, &abi)| (func[value].ty, abi))
422 .collect();
423 let spills =
424 forwardable(convention, &outer, &before, &forwarded, plan.groups.as_deref())
425 .ok_or(InlineFailure::Pack)?;
426 if !spills.is_empty() {
427 plan.spills.insert(inst, spills);
428 }
429 }
430 }
431 Ok(plan)
432}
433
434fn is_pack(func: &Func, value: Value) -> bool {
436 matches!(func[value].def, Def::Result { inst, .. } if func[inst].opcode == Opcode::VaArgPack)
437}
438
439fn expand(abis: &[Abi], count: usize) -> Vec<Abi> {
442 if abis.is_empty() { vec![Abi::Plain; count] } else { abis.to_vec() }
443}
444
445fn past(groups: &[u32], fixed: usize) -> Option<Vec<u32>> {
448 let mut seen = 0;
449 let mut rest = groups.iter();
450 while seen < fixed {
451 seen += usize::try_from(*rest.next()?).ok()?;
452 }
453 (seen == fixed).then(|| rest.copied().collect())
454}
455
456#[derive(Debug, Clone, Copy, PartialEq, Eq)]
458enum Convention {
459 SysV,
461 Slots,
463 Other,
465}
466
467impl Convention {
468 fn of(module: &Module) -> Self {
469 match (module.tuple.arch(), module.tuple.os()) {
470 (Arch::X86_64, Os::Windows) => Self::Slots,
471 (Arch::X86_64, _) => Self::SysV,
472 _ => Self::Other,
473 }
474 }
475}
476
477#[derive(Debug, Clone, Copy, PartialEq, Eq)]
479enum Class {
480 Gpr(u32),
482 Sse,
484 Memory,
486}
487
488fn class(ty: Type, abi: Abi) -> Class {
489 if abi.indirect() && !matches!(abi, Abi::Sret { .. }) {
490 Class::Memory
491 } else if ty.is_vector() {
492 Class::Sse
493 } else if ty.is_float() {
494 if ty.format() == Some(Float::F80) { Class::Memory } else { Class::Sse }
495 } else if ty.is_int() && ty.bits() > 64 {
496 Class::Gpr(2)
497 } else {
498 Class::Gpr(1)
499 }
500}
501
502#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
504struct Regs {
505 gpr: u32,
506 sse: u32,
507}
508
509impl Regs {
510 const GPR: u32 = 6;
511 const SSE: u32 = 8;
512
513 fn after(values: &[(Type, Abi)]) -> Self {
514 let mut regs = Self::default();
515 for &(ty, abi) in values {
516 regs.take(class(ty, abi));
517 }
518 regs
519 }
520
521 fn fits(self, gpr: u32, sse: u32) -> bool {
522 self.gpr + gpr <= Self::GPR && self.sse + sse <= Self::SSE
523 }
524
525 fn take(&mut self, class: Class) -> bool {
527 let (gpr, sse) = match class {
528 Class::Gpr(count) => (count, 0),
529 Class::Sse => (0, 1),
530 Class::Memory => return false,
531 };
532 let room = self.fits(gpr, sse);
533 if room {
534 self.gpr += gpr;
535 self.sse += sse;
536 }
537 room
538 }
539}
540
541fn forwardable(
554 convention: Convention,
555 outer: &[(Type, Abi)],
556 before: &[(Type, Abi)],
557 forwarded: &[(Type, Abi)],
558 groups: Option<&[u32]>,
559) -> Option<Vec<usize>> {
560 match convention {
561 Convention::Slots => Some(Vec::new()),
562 Convention::Other => {
563 let count = |values: &[(Type, Abi)]| {
564 let mut ints = 0;
565 let mut floats = 0;
566 for &(ty, abi) in values {
567 if abi.indirect() {
568 return None;
569 }
570 if ty.is_float() || ty.is_vector() { floats += 1 } else { ints += 1 }
571 }
572 Some((ints, floats))
573 };
574 (count(outer).is_some() && count(outer) == count(before)).then(Vec::new)
575 }
576 Convention::SysV => {
577 let mut first = Regs::after(outer);
578 let mut second = Regs::after(before);
579 if first == second {
580 return Some(Vec::new());
581 }
582 let mut spills = Vec::new();
583 let mut at = 0;
584 for (index, &count) in groups?.iter().enumerate() {
585 let group = usize::try_from(count).ok().and_then(|n| forwarded.get(at..at + n))?;
586 at += group.len();
587 match *group {
588 [] => {}
589 [(ty, abi)] => {
593 if let Abi::ByVal { size, .. } = abi {
594 let small = size <= 16; if small && (second.gpr < first.gpr || second.sse < first.sse) {
596 return None;
597 }
598 continue;
599 }
600 first.take(class(ty, abi));
601 second.take(class(ty, abi));
602 }
603 _ => {
606 let mut gpr = 0;
607 let mut sse = 0;
608 for &(ty, abi) in group {
609 match class(ty, abi) {
610 Class::Gpr(count) => gpr += count,
611 Class::Sse => sse += 1,
612 Class::Memory => return None,
613 }
614 }
615 if !first.fits(gpr, sse) {
616 return None;
617 }
618 first.gpr += gpr;
619 first.sse += sse;
620 if second.fits(gpr, sse) {
621 second.gpr += gpr;
622 second.sse += sse;
623 } else {
624 spills.push(index);
625 }
626 }
627 }
628 }
629 (at == forwarded.len()).then_some(spills)
630 }
631 }
632}
633
634fn copy(func: &mut Func, call: Inst, callee: &Func, plan: &Plan) {
636 let block = func.block_of(call).expect("a call being inlined is in a block");
637 let entry = func.entry().expect("a function with a call in it has a body");
638
639 let after = func.create_block();
641 let mut forward = HashMap::new();
642 for result in func[call].results().collect::<Vec<Value>>() {
643 let ty = func[result].ty;
644 forward.insert(result, func.append_param(after, ty));
645 }
646 let moving: Vec<Inst> = func.insts(block).skip_while(|&inst| inst != call).skip(1).collect();
647 for inst in moving {
648 func.remove_inst(inst);
649 func.append_inst(after, inst);
650 }
651
652 let start = callee.entry().expect("checked to have a body");
661 let passed = func[func[call].args][..plan.fixed].to_vec();
662 let mut blocks = HashMap::new();
663 let mut values = HashMap::new();
664 for from in callee.blocks() {
665 let to = func.create_block();
666 if from == start {
667 values.extend(callee[from].params.iter().copied().zip(passed.iter().copied()));
668 } else {
669 for ¶m in &callee[from].params {
670 values.insert(param, func.append_param(to, callee[param].ty));
671 }
672 }
673 blocks.insert(from, to);
674 }
675 let mut made = Vec::new();
676 for from in callee.blocks() {
677 for inst in callee.insts(from) {
678 let data = &callee[inst];
679 if data.opcode == Opcode::VaArgPack {
680 continue;
681 }
682 let opcode = match data.opcode {
683 Opcode::Return => Opcode::Jump,
684 Opcode::VaArgPackLen => Opcode::IConst,
685 opcode => opcode,
686 };
687 let types: Vec<Type> = data.results().map(|value| callee[value].ty).collect();
688 let shell = InstData { flags: data.flags, ..InstData::new(opcode) };
689 let new = func.create_inst(shell, &types, callee.span(inst));
690 for (old, value) in data.results().zip(func[new].results().collect::<Vec<Value>>()) {
691 values.insert(old, value);
692 }
693 if opcode == Opcode::Alloca && data.args.is_empty() {
694 let first = func.insts(entry).next().expect("an entry block ends in something");
695 func.insert_before(new, first);
696 } else {
697 func.append_inst(blocks[&from], new);
698 }
699 made.push((inst, new));
700 }
701 }
702
703 let keep = func[call].results().count();
704 for (inst, new) in made {
705 let data = &callee[inst];
706 let mut args: Vec<Value> = callee[data.args]
707 .iter()
708 .filter(|value| values.contains_key(value))
709 .map(|value| values[value])
710 .collect();
711 let packed = args.len() != data.args.len();
712 let extra = if data.opcode == Opcode::Return {
713 args.truncate(keep);
714 let to = func.push_values(&args);
715 args.clear();
716 Extra::Targets(func.push_block_calls(&[BlockCall::new(after, to)]))
717 } else if data.opcode == Opcode::VaArgPackLen {
718 let count = plan.groups.as_ref().map_or(plan.extras.len(), Vec::len);
721 let count = i128::try_from(count).expect("fewer arguments than that");
722 Extra::Imm(func.add_imm(Imm::int(count, Type::int(32))))
723 } else {
724 match data.extra {
725 Extra::Imm(imm) => Extra::Imm(func.add_imm(callee[imm])),
726 Extra::Mem(mem) => Extra::Mem(func.add_mem(unscoped(callee[mem]))),
727 Extra::Rmw(op, mem) => Extra::Rmw(op, func.add_mem(unscoped(callee[mem]))),
728 Extra::Targets(list) => {
729 Extra::Targets(targets(func, callee, list, &blocks, &values))
730 }
731 Extra::Call(info) => {
732 let info = callee[info];
733 let mut forwarded = None;
734 let signature = callee[info.signature].clone();
735 let mut abis = callee[info.varargs].to_vec();
736 if packed {
737 let skip = usize::from(data.opcode == Opcode::CallIndirect);
738 let written = args.len() - skip - signature.params.len();
739 abis = expand(&abis, written + 1);
740 abis.truncate(written);
741 let spills = plan.spills.get(&inst).map_or(&[][..], Vec::as_slice);
742 forwarded = pass_on(func, entry, new, spills, plan, &mut args, &mut abis);
743 if abis.iter().all(|&abi| abi == Abi::Plain) {
744 abis.clear();
745 }
746 }
747 let signature = func.add_signature(signature);
748 let varargs = func.push_abis(&abis);
749 if let Some(groups) = callee.arg_groups(inst) {
750 let mut groups = groups.to_vec();
751 let known = if packed {
752 groups.pop();
753 forwarded.as_ref().map(|outer| groups.extend_from_slice(outer))
754 } else {
755 Some(())
756 };
757 if known.is_some() {
758 func.set_arg_groups(new, groups);
759 }
760 }
761 Extra::Call(func.add_call(CallInfo { callee: info.callee, signature, varargs }))
762 }
763 Extra::Switch(info) => {
764 let info = callee[info];
765 let cases = func.push_imms(&callee[info.cases]);
766 let targets = targets(func, callee, info.targets, &blocks, &values);
767 Extra::Switch(func.add_switch(SwitchInfo { targets, cases }))
768 }
769 Extra::Asm(info) => {
770 let info = callee[info];
771 let targets = targets(func, callee, info.targets, &blocks, &values);
772 Extra::Asm(func.add_asm(AsmInfo { targets, ..info }))
773 }
774 Extra::VaObject(info) => {
775 let info = callee[info];
776 let mem = func.add_mem(unscoped(callee[info.mem]));
777 let slots = func.push_slots(&callee[info.slots]);
778 Extra::VaObject(func.add_va_object(VaInfo { mem, slots }))
779 }
780 other => other,
781 }
782 };
783 func[new].args = if args.is_empty() { ValueList::EMPTY } else { func.push_values(&args) };
784 func[new].extra = extra;
785 }
786
787 let to = ValueList::EMPTY;
789 let targets = func.push_block_calls(&[BlockCall::new(blocks[&start], to)]);
790 let span = func.span(call);
791 let jump = func.create_inst(
792 InstData { extra: Extra::Targets(targets), ..InstData::new(Opcode::Jump) },
793 &[],
794 span,
795 );
796 crate::uses::substitute(func, &forward);
797 func.remove_inst(call);
798 func.append_inst(block, jump);
799}
800
801fn pass_on(
805 func: &mut Func,
806 entry: Block,
807 call: Inst,
808 spills: &[usize],
809 plan: &Plan,
810 args: &mut Vec<Value>,
811 abis: &mut Vec<Abi>,
812) -> Option<Vec<u32>> {
813 let Some(groups) = plan.groups.as_deref().filter(|_| !spills.is_empty()) else {
814 args.extend_from_slice(&plan.extras);
815 abis.extend_from_slice(&plan.abis);
816 return plan.groups.clone();
817 };
818 let mut now = Vec::with_capacity(groups.len());
819 let mut at = 0;
820 for (index, &count) in groups.iter().enumerate() {
821 let end = at + count as usize;
822 if spills.contains(&index) {
823 let (slot, size) = spill(func, entry, call, &plan.extras[at..end]);
824 args.push(slot);
825 abis.push(Abi::ByVal { size, align: 8, drains: Drains::Nothing });
826 now.push(1);
827 } else {
828 args.extend_from_slice(&plan.extras[at..end]);
829 abis.extend_from_slice(&plan.abis[at..end]);
830 now.push(count);
831 }
832 at = end;
833 }
834 Some(now)
835}
836
837fn spill(func: &mut Func, entry: Block, call: Inst, pieces: &[Value]) -> (Value, u64) {
840 let span = func.span(call);
841 let bytes = |ty: Type| {
842 if ty == Type::PTR { 8 } else { u64::from(ty.bits() * ty.lanes()).div_ceil(8) }
843 };
844 let size: u64 = pieces.iter().map(|&piece| bytes(func[piece].ty).next_multiple_of(8)).sum();
845 let info = MemInfo {
846 size,
847 align: 8,
848 order: MemOrder::NotAtomic,
849 tbaa: None,
850 owns: 0,
851 restrict: Restrict::NONE,
852 };
853 let mem = func.add_mem(info);
854 let alloca = InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) };
855 let alloca = func.create_inst(alloca, &[Type::PTR], span);
856 let first = func.insts(entry).next().expect("an entry block ends in something");
857 func.insert_before(alloca, first);
858 let slot = func[alloca].results().next().expect("an alloca has a result");
859
860 let mut offset = 0;
861 for &piece in pieces {
862 let ty = func[piece].ty;
863 let width = bytes(ty);
864 let mut address = slot;
865 if offset != 0 {
866 let imm = func.add_imm(Imm::int(i128::from(offset), Type::int(64)));
867 let amount = InstData { extra: Extra::Imm(imm), ..InstData::new(Opcode::IConst) };
868 let amount = func.create_inst(amount, &[Type::int(64)], span);
869 func.insert_before(amount, call);
870 let amount = func[amount].results().next().expect("a constant has a result");
871 let add = InstData {
872 args: func.push_values(&[slot, amount]),
873 ..InstData::new(Opcode::PtrAdd)
874 };
875 let add = func.create_inst(add, &[Type::PTR], span);
876 func.insert_before(add, call);
877 address = func[add].results().next().expect("an address has a result");
878 }
879 let info = MemInfo { size: width, ..info };
880 let store = InstData {
881 args: func.push_values(&[piece, address]),
882 extra: Extra::Mem(func.add_mem(info)),
883 ..InstData::new(Opcode::Store)
884 };
885 let store = func.create_inst(store, &[], span);
886 func.insert_before(store, call);
887 offset += width.next_multiple_of(8);
888 }
889 (slot, size)
890}
891
892fn unscoped(info: MemInfo) -> MemInfo {
895 MemInfo { restrict: Restrict::NONE, ..info }
896}
897
898fn targets(
900 func: &mut Func,
901 callee: &Func,
902 list: BlockCallList,
903 blocks: &HashMap<Block, Block>,
904 values: &HashMap<Value, Value>,
905) -> BlockCallList {
906 let calls: Vec<BlockCall> = callee[list]
907 .iter()
908 .map(|call| {
909 let args: Vec<Value> = callee[call.args].iter().map(|value| values[value]).collect();
910 let args = func.push_values(&args);
911 BlockCall { block: blocks[&call.block], args, hint: call.hint }
912 })
913 .collect();
914 func.push_block_calls(&calls)
915}
916
917fn withdraw(module: &mut Module) {
920 for id in module.funcs().collect::<Vec<FuncId>>() {
921 let func = &module[id];
922 let holds = func
923 .blocks()
924 .flat_map(|block| func.insts(block))
925 .any(|inst| matches!(func[inst].opcode, Opcode::VaArgPack | Opcode::VaArgPackLen));
926 if !holds {
927 continue;
928 }
929 let mut declared = Func::new(func.name, func.signature().clone());
930 declared.spelled = func.spelled;
931 declared.visibility = func.visibility;
932 declared.attrs = func.attrs;
933 declared.declared = func.declared;
934 declared.linkage = Linkage::External;
935 module[id] = declared;
936 }
937}
938
939#[cfg(test)]
940mod tests {
941 use rucc_base::Interner;
942
943 use super::*;
944
945 const HEAD: &str = r#"; ModuleID = 't.c'
946; format 0
947target triple = "x86_64-unknown-linux-gnu"
948target datalayout = "e-p:64:64-i64:64-f80:128-S128"
949"#;
950
951 fn inlined(body: &str) -> String {
952 inlined_under(body, None)
953 }
954
955 fn inlined_under(body: &str, limit: Option<u32>) -> String {
956 let mut names = Interner::new();
957 let text = format!("{HEAD}{body}");
958 let mut module = rucc_ir::parse(&text, &mut names).expect("the fixture parses");
959 run(&mut module, limit);
960 if let Err(errors) = rucc_ir::verify(&module, &names) {
961 panic!("the inliner left invalid IR, {errors:?}\n{}", rucc_ir::print(&module, &names));
962 }
963 rucc_ir::print(&module, &names)
964 }
965
966 #[test]
969 fn a_call_to_an_always_inline_function_is_replaced_by_its_body() {
970 let out = inlined(
971 r#"
972func @twice(i32) -> i32, linkage(linkonce), attrs(always_inline) {
973block0(%0: i32):
974 %1 = alloca, size 4, align 4
975 %2 = add.i32 %0, %0
976 return %2
977}
978
979func @g(i32) -> i32, linkage(external) {
980block0(%0: i32):
981 %1 = call @twice(%0) : (i32) -> i32
982 %2 = add.i32 %1, %1
983 return %2
984}
985"#,
986 );
987 let g = &out[out.find("func @g").expect("g is there")..];
988 assert!(!g.contains("call @twice"), "{out}");
989 assert!(g.contains("alloca"), "{out}");
990 }
991
992 #[test]
995 fn a_pack_is_the_anonymous_arguments_of_the_call_inlined() {
996 let out = inlined(
997 r#"
998func @inner(i32, ...) -> i32, linkage(external);
999
1000func @wrap(i32, ...) -> i32, linkage(linkonce), attrs(always_inline) {
1001block0(%0: i32):
1002 %1 = va_arg_pack.i32
1003 %2 = call @inner(%0, %1) : (i32, ...) -> i32
1004 return %2
1005}
1006
1007func @g(i64, f64) -> i32, linkage(external) {
1008block0(%0: i64, %1: f64):
1009 %2 = iconst.i32 7
1010 %3 = call @wrap(%2, %0, %1) : (i32, ...) -> i32
1011 return %3
1012}
1013"#,
1014 );
1015 assert!(
1016 out.contains("func @wrap(i32, ...) -> i32, linkage(external), attrs(always_inline);"),
1017 "{out}"
1018 );
1019 assert!(!out.contains("va_arg_pack"), "{out}");
1020 assert!(out.contains("call @inner(%"), "{out}");
1021 }
1022
1023 #[test]
1025 fn a_pack_length_is_the_count_of_the_anonymous_arguments() {
1026 let out = inlined(
1027 r#"
1028func @wrap(i32, ...) -> i32, linkage(linkonce), attrs(always_inline) {
1029block0(%0: i32):
1030 %1 = va_arg_pack_len.i32
1031 return %1
1032}
1033
1034func @g(i64, f64) -> i32, linkage(external) {
1035block0(%0: i64, %1: f64):
1036 %2 = iconst.i32 7
1037 %3 = call @wrap(%2, %0, %1) : (i32, ...) -> i32
1038 return %3
1039}
1040"#,
1041 );
1042 let g = &out[out.find("func @g").expect("g is there")..];
1043 assert!(g.contains("iconst.i32 2"), "{out}");
1044 assert!(!g.contains("call @wrap"), "{out}");
1045 }
1046
1047 const HINTED: &str = r#"
1049func @bump(i32) -> i32, linkage(external), attrs(inline_hint) {
1050block0(%0: i32):
1051 %1 = iconst.i32 1
1052 %2 = add.i32 %0, %1
1053 return %2
1054}
1055
1056func @g(i32) -> i32, linkage(external) {
1057block0(%0: i32):
1058 %1 = call @bump(%0) : (i32) -> i32
1059 return %1
1060}
1061"#;
1062
1063 #[test]
1066 fn a_small_function_declared_inline_is_inlined_above_o0() {
1067 let out = inlined_under(HINTED, Some(70));
1068 let g = &out[out.find("func @g").expect("g is there")..];
1069 assert!(!g.contains("call @bump"), "{out}");
1070 let out = inlined_under(HINTED, None);
1071 assert!(out.contains("call @bump"), "{out}");
1072 }
1073
1074 #[test]
1076 fn a_function_declared_inline_over_the_limit_is_left_alone() {
1077 let out = inlined_under(HINTED, Some(2));
1078 assert!(out.contains("call @bump"), "{out}");
1079 }
1080
1081 #[test]
1084 fn each_copy_of_a_label_address_is_a_label_of_its_own() {
1085 let out = inlined_under(
1086 r#"
1087func @here() -> ptr, linkage(internal), attrs(inline_hint) {
1088block0:
1089 jump block1
1090block1:
1091 %0 = block_addr block1
1092 return %0
1093}
1094
1095func @g() -> i1, linkage(external) {
1096block0:
1097 %0 = call @here() : () -> ptr
1098 %1 = call @here() : () -> ptr
1099 %2 = icmp eq %0, %1
1100 return %2
1101}
1102"#,
1103 Some(70),
1104 );
1105 let g = &out[out.find("func @g").expect("g is there")..];
1106 assert!(!g.contains("call @here"), "{out}");
1107 assert_eq!(g.matches("block_addr").count(), 2, "{out}");
1108 }
1109
1110 #[test]
1113 fn a_computed_goto_is_not_inlined() {
1114 let out = inlined_under(
1115 r#"
1116func @jump(ptr) -> i32, linkage(internal), attrs(inline_hint) {
1117block0(%0: ptr):
1118 indirect_br %0, block1
1119block1:
1120 %1 = iconst.i32 1
1121 return %1
1122}
1123
1124func @g(ptr) -> i32, linkage(external) {
1125block0(%0: ptr):
1126 %1 = call @jump(%0) : (ptr) -> i32
1127 return %1
1128}
1129"#,
1130 Some(70),
1131 );
1132 assert!(out.contains("call @jump"), "{out}");
1133 }
1134
1135 #[test]
1137 fn a_recursive_always_inline_function_is_left_alone() {
1138 let out = inlined(
1139 r#"
1140func @r(i32) -> i32, linkage(linkonce), attrs(always_inline) {
1141block0(%0: i32):
1142 %1 = call @r(%0) : (i32) -> i32
1143 return %1
1144}
1145"#,
1146 );
1147 assert!(out.contains("call @r("), "{out}");
1148 }
1149
1150 #[test]
1153 fn a_structure_that_would_straddle_the_registers_goes_to_memory() {
1154 let int = (Type::int(64), Abi::Plain);
1155 let outer = [int];
1156 let before = [int, int, int, int, int];
1157 let forwarded = [int, int];
1158 let sysv = |before: &[(Type, Abi)], groups| {
1159 forwardable(Convention::SysV, &outer, before, &forwarded, groups)
1160 };
1161 assert_eq!(sysv(&before, Some(&[2])), Some(vec![0]));
1162 assert_eq!(sysv(&before, Some(&[1, 1])), Some(Vec::new()));
1163 assert_eq!(sysv(&before, None), None);
1164 assert_eq!(sysv(&outer, None), Some(Vec::new()));
1165 }
1166}