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