1use rucc_base::{Interner, Symbol};
37use rucc_ir::{Abi, AttrSet, Extra, Func, Inst, Opcode, Value};
38use rucc_mir as mir;
39use rucc_target::{FrameInsts, RegClass};
40
41use crate::elsewhere::Elsewhere;
42
43#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct Tail {
47 pub call: mir::Inst,
49 pub returns: Vec<mir::Inst>,
51}
52
53#[must_use]
59pub fn refusal(func: &Func, names: &Interner, elsewhere: &Elsewhere) -> Option<&'static str> {
60 if func.attrs.set.contains(AttrSet::NAKED) {
61 return Some("the function is naked and writes its own ending");
62 }
63 let sret =
64 func.signature().params.first().is_some_and(|param| matches!(param.abi, Abi::Sret { .. }));
65 if sret {
66 return Some("the function gives its answer back through memory it was handed");
67 }
68 for block in func.blocks() {
69 for inst in func.insts(block) {
70 match func[inst].opcode {
71 Opcode::Alloca => return Some("a local lives in the frame"),
72 Opcode::VaStart => return Some("the function reads its own variable arguments"),
73 Opcode::ApplyArgs => return Some("the function keeps its arguments in the frame"),
74 Opcode::SetjmpMarker => return Some("the function saves a place to come back to"),
75 Opcode::Call if twice(func, inst, names, elsewhere) => {
76 return Some("the function calls something that comes back twice");
77 }
78 _ => (),
79 }
80 }
81 }
82 None
83}
84
85#[must_use]
98pub fn comes_back(func: &Func, names: &Interner, elsewhere: &Elsewhere) -> bool {
99 func.blocks().any(|block| {
100 func.insts(block).any(|inst| match func[inst].opcode {
101 Opcode::SetjmpMarker => true,
102 Opcode::Call => twice(func, inst, names, elsewhere),
103 _ => false,
104 })
105 })
106}
107
108fn twice(func: &Func, inst: Inst, names: &Interner, elsewhere: &Elsewhere) -> bool {
111 let Extra::Call(info) = func[inst].extra else { return false };
112 let Some(callee) = func[info].callee else { return false };
113 elsewhere.twice(callee) || rucc_ir::twice_by_name(names.resolve(callee))
114}
115
116pub fn mark(func: &mut Func, names: &Interner, elsewhere: &Elsewhere) -> usize {
119 if refusal(func, names, elsewhere).is_some() {
120 return 0;
121 }
122 let blocks: Vec<_> = func.blocks().collect();
123 let mut marked = 0;
124 for block in blocks {
125 let insts: Vec<Inst> = func.insts(block).collect();
126 let [.., call, ret] = insts[..] else { continue };
127 if !in_tail_position(func, call, ret) {
128 continue;
129 }
130 func.remove_inst(ret);
131 func[call].opcode = Opcode::TailCall;
132 marked += 1;
133 }
134 marked
135}
136
137fn in_tail_position(func: &Func, call: Inst, ret: Inst) -> bool {
144 if func[ret].opcode != Opcode::Return || func[call].opcode != Opcode::Call {
145 return false;
146 }
147 let Extra::Call(info) = func[call].extra else { return false };
148 let info = func[info];
149 if info.callee.is_none() {
150 return false;
151 }
152 let results: Vec<Value> = func[call].results().collect();
153 if func[func[ret].args] != results[..] {
154 return false;
155 }
156 let callee = &func[info.signature];
163 callee.convention == func.signature().convention && callee.returns == func.signature().returns
164}
165
166pub fn jumps(
170 func: &mut mir::Func,
171 tails: &[Tail],
172 insts: &FrameInsts,
173 names: &mut Interner,
174) -> usize {
175 let Some(away) = insts.away else { return 0 };
176 let ret = mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.ret)));
177 let away = mir::Opcode::new(names.intern(&format!("{}{away}", insts.prefix)));
178 let mut jumped = 0;
179 for tail in tails {
180 let Some((last, callee)) = ending(func, tail, ret) else { continue };
181 let span = func.span(tail.call);
182 func.remove_inst(tail.call);
183 for &pseudo in &tail.returns {
184 func.remove_inst(pseudo);
185 }
186 func[last].opcode = away;
189 func[last].symbol = Some(callee);
190 func.set_span(last, span);
191 jumped += 1;
192 }
193 jumped
194}
195
196fn ending(func: &mir::Func, tail: &Tail, ret: mir::Opcode) -> Option<(mir::Inst, Symbol)> {
204 let block = func.block_of(tail.call)?;
205 let callee = func[tail.call].symbol?;
206 if !func[block].succs.is_empty() {
207 return None;
208 }
209 let last = func.insts(block).last()?;
210 if func[last].opcode != ret {
211 return None;
212 }
213 let named: Vec<(mir::Reg, RegClass)> =
214 func[func[tail.call].operands].iter().map(|operand| (operand.reg, operand.class)).collect();
215 let mut at = func.next_inst(tail.call)?;
216 while at != last {
217 if !tail.returns.contains(&at) {
218 let data = &func[at];
219 let touches = func[data.operands]
220 .iter()
221 .any(|operand| named.contains(&(operand.reg, operand.class)));
222 if touches || data.symbol.is_some() {
223 return None;
224 }
225 }
226 at = func.next_inst(at)?;
227 }
228 Some((last, callee))
229}
230
231#[cfg(test)]
232mod tests {
233 use rucc_base::Interner;
234 use rucc_ir::{Block, Builder, Flags, Func, InstData, Opcode, Signature, Type, Value};
235
236 use super::{comes_back, mark, refusal};
237 use crate::elsewhere::Elsewhere;
238
239 fn caller(
241 names: &mut Interner,
242 between: impl FnOnce(&mut Func, Block, Value) -> Value,
243 ) -> Func {
244 caller_of(names, rucc_target::Convention::Target, between)
245 }
246
247 fn caller_of(
249 names: &mut Interner,
250 convention: rucc_target::Convention,
251 between: impl FnOnce(&mut Func, Block, Value) -> Value,
252 ) -> Func {
253 let i32 = Type::int(32);
254 let mut func =
255 Func::new(names.intern("f"), Signature::new().with_params(&[i32]).with_returns(&[i32]));
256 let block = func.create_block();
257 let arg = func.append_param(block, i32);
258 let mut called = Signature::new().with_params(&[i32]).with_returns(&[i32]);
259 called.convention = convention;
260 let sig = func.add_signature(called);
261 let callee = names.intern("g");
262 let call = Builder::new(&mut func, block).call(callee, sig, &[arg]);
263 let got = func[call].first_result.expect("an integer comes back");
264 let answer = between(&mut func, block, got);
265 Builder::new(&mut func, block).ret(&[answer]);
266 func
267 }
268
269 fn opcodes(func: &Func) -> Vec<Opcode> {
270 func.blocks()
271 .flat_map(|block| func.insts(block).map(|inst| func[inst].opcode).collect::<Vec<_>>())
272 .collect()
273 }
274
275 #[test]
278 fn a_call_to_the_other_convention_stays_a_call() {
279 let mut names = Interner::new();
280 let mut func = caller_of(&mut names, rucc_target::Convention::Ms, |_, _, got| got);
281 assert_eq!(mark(&mut func, &names, &Elsewhere::default()), 0);
282 assert_eq!(opcodes(&func), [Opcode::Call, Opcode::Return]);
283 }
284
285 #[test]
286 fn a_call_whose_answer_is_returned_becomes_a_tail_call() {
287 let mut names = Interner::new();
288 let mut func = caller(&mut names, |_, _, got| got);
289 assert_eq!(mark(&mut func, &names, &Elsewhere::default()), 1);
290 assert_eq!(opcodes(&func), [Opcode::TailCall]);
291 }
292
293 #[test]
294 fn a_call_with_work_after_it_is_not_in_tail_position() {
295 let mut names = Interner::new();
296 let mut func = caller(&mut names, |func, block, got| {
297 Builder::new(func, block).binary(Opcode::Add, got, got, Flags::default())
298 });
299 assert_eq!(mark(&mut func, &names, &Elsewhere::default()), 0);
300 assert_eq!(opcodes(&func), [Opcode::Call, Opcode::Add, Opcode::Return]);
301 }
302
303 #[test]
304 fn a_local_in_the_frame_turns_down_the_whole_function() {
305 let mut names = Interner::new();
306 let mut func = caller(&mut names, |func, block, got| {
307 Builder::new(func, block).value(InstData::new(Opcode::Alloca), Type::PTR);
308 got
309 });
310 assert_eq!(
311 refusal(&func, &names, &Elsewhere::default()),
312 Some("a local lives in the frame")
313 );
314 assert_eq!(mark(&mut func, &names, &Elsewhere::default()), 0);
315 }
316
317 #[test]
318 fn a_call_to_setjmp_anywhere_turns_down_the_whole_function() {
319 let mut names = Interner::new();
320 let setjmp = names.intern("_setjmp");
321 let mut func = caller(&mut names, |func, block, got| {
322 let sig = func.add_signature(Signature::new().with_returns(&[Type::int(32)]));
323 Builder::new(func, block).call(setjmp, sig, &[]);
324 got
325 });
326 assert_eq!(
327 refusal(&func, &names, &Elsewhere::default()),
328 Some("the function calls something that comes back twice")
329 );
330 assert_eq!(mark(&mut func, &names, &Elsewhere::default()), 0);
331 }
332
333 #[test]
334 fn glibcs_name_for_sigsetjmp_comes_back_and_an_ordinary_call_does_not() {
335 let mut names = Interner::new();
336 let setjmp = names.intern("__sigsetjmp");
337 let func = caller(&mut names, |func, block, got| {
338 let sig = func.add_signature(Signature::new().with_returns(&[Type::int(32)]));
339 Builder::new(func, block).call(setjmp, sig, &[]);
340 got
341 });
342 assert!(comes_back(&func, &names, &Elsewhere::default()));
343 let plain = caller(&mut names, |_, _, got| got);
344 assert!(!comes_back(&plain, &names, &Elsewhere::default()));
345 }
346}