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