1use 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
78pub const NAME: &str = "inline";
81
82const INLINED: &str = "always_inline call inlined";
83
84const HINT_INLINED: &str = "inline call inlined";
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88enum Kind {
89 Always,
91 Hinted,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum InlineFailure {
98 Recursive,
100 Mismatch,
102 ByValue,
104 VaStart,
106 ComputedGoto,
109 Setjmp,
111 ApplyArgs,
113 MemorySsa,
115 Pack,
117 Alloca,
120 TooLarge,
122}
123
124impl InlineFailure {
125 #[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 #[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
162pub 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
200struct How<'a> {
202 wanted: &'a HashMap<Symbol, (FuncId, Kind)>,
204 convention: Convention,
206 limit: usize,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212enum State {
213 Settling,
215 Settled,
217}
218
219fn 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 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 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
279fn size(func: &Func) -> usize {
281 func.blocks().map(|block| func.insts(block).count()).sum()
282}
283
284fn splice(
286 module: &mut Module,
287 caller: FuncId,
288 call: Inst,
289 callee: FuncId,
290 convention: Convention,
291 kind: Kind,
292) -> Result<(), InlineFailure> {
293 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
304struct Plan {
306 fixed: usize,
308 extras: Vec<Value>,
310 abis: Vec<Abi>,
312 groups: Option<Vec<u32>>,
314 spills: HashMap<Inst, Vec<usize>>,
317}
318
319fn 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, ¶m)| 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 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
437fn is_pack(func: &Func, value: Value) -> bool {
439 matches!(func[value].def, Def::Result { inst, .. } if func[inst].opcode == Opcode::VaArgPack)
440}
441
442fn expand(abis: &[Abi], count: usize) -> Vec<Abi> {
445 if abis.is_empty() { vec![Abi::Plain; count] } else { abis.to_vec() }
446}
447
448fn 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
461enum Convention {
462 SysV,
464 Slots,
466 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
482enum Class {
483 Gpr(u32),
485 Sse,
487 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#[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 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
544fn 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 [(ty, abi)] => {
596 if let Abi::ByVal { size, .. } = abi {
597 let small = size <= 16; 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 _ => {
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
637fn 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 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 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 ¶m 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 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 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
804fn 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
840fn 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
895fn unscoped(info: MemInfo) -> MemInfo {
898 MemInfo { restrict: Restrict::NONE, ..info }
899}
900
901fn 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
920fn 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 #[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 #[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 #[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 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 #[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 #[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 #[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 #[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 #[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 #[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}