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 func[info.signature].returns == func.signature().returns
162}
163
164pub fn jumps(
168 func: &mut mir::Func,
169 tails: &[Tail],
170 insts: &FrameInsts,
171 names: &mut Interner,
172) -> usize {
173 let Some(away) = insts.away else { return 0 };
174 let ret = mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.ret)));
175 let away = mir::Opcode::new(names.intern(&format!("{}{away}", insts.prefix)));
176 let mut jumped = 0;
177 for tail in tails {
178 let Some((last, callee)) = ending(func, tail, ret) else { continue };
179 let span = func.span(tail.call);
180 func.remove_inst(tail.call);
181 for &pseudo in &tail.returns {
182 func.remove_inst(pseudo);
183 }
184 func[last].opcode = away;
187 func[last].symbol = Some(callee);
188 func.set_span(last, span);
189 jumped += 1;
190 }
191 jumped
192}
193
194fn ending(func: &mir::Func, tail: &Tail, ret: mir::Opcode) -> Option<(mir::Inst, Symbol)> {
202 let block = func.block_of(tail.call)?;
203 let callee = func[tail.call].symbol?;
204 if !func[block].succs.is_empty() {
205 return None;
206 }
207 let last = func.insts(block).last()?;
208 if func[last].opcode != ret {
209 return None;
210 }
211 let named: Vec<(mir::Reg, RegClass)> =
212 func[func[tail.call].operands].iter().map(|operand| (operand.reg, operand.class)).collect();
213 let mut at = func.next_inst(tail.call)?;
214 while at != last {
215 if !tail.returns.contains(&at) {
216 let data = &func[at];
217 let touches = func[data.operands]
218 .iter()
219 .any(|operand| named.contains(&(operand.reg, operand.class)));
220 if touches || data.symbol.is_some() {
221 return None;
222 }
223 }
224 at = func.next_inst(at)?;
225 }
226 Some((last, callee))
227}
228
229#[cfg(test)]
230mod tests {
231 use rucc_base::Interner;
232 use rucc_ir::{Block, Builder, Flags, Func, InstData, Opcode, Signature, Type, Value};
233
234 use super::{comes_back, mark, refusal};
235 use crate::elsewhere::Elsewhere;
236
237 fn caller(
239 names: &mut Interner,
240 between: impl FnOnce(&mut Func, Block, Value) -> Value,
241 ) -> Func {
242 let i32 = Type::int(32);
243 let mut func =
244 Func::new(names.intern("f"), Signature::new().with_params(&[i32]).with_returns(&[i32]));
245 let block = func.create_block();
246 let arg = func.append_param(block, i32);
247 let sig = func.add_signature(Signature::new().with_params(&[i32]).with_returns(&[i32]));
248 let callee = names.intern("g");
249 let call = Builder::new(&mut func, block).call(callee, sig, &[arg]);
250 let got = func[call].first_result.expect("an integer comes back");
251 let answer = between(&mut func, block, got);
252 Builder::new(&mut func, block).ret(&[answer]);
253 func
254 }
255
256 fn opcodes(func: &Func) -> Vec<Opcode> {
257 func.blocks()
258 .flat_map(|block| func.insts(block).map(|inst| func[inst].opcode).collect::<Vec<_>>())
259 .collect()
260 }
261
262 #[test]
263 fn a_call_whose_answer_is_returned_becomes_a_tail_call() {
264 let mut names = Interner::new();
265 let mut func = caller(&mut names, |_, _, got| got);
266 assert_eq!(mark(&mut func, &names, &Elsewhere::default()), 1);
267 assert_eq!(opcodes(&func), [Opcode::TailCall]);
268 }
269
270 #[test]
271 fn a_call_with_work_after_it_is_not_in_tail_position() {
272 let mut names = Interner::new();
273 let mut func = caller(&mut names, |func, block, got| {
274 Builder::new(func, block).binary(Opcode::Add, got, got, Flags::default())
275 });
276 assert_eq!(mark(&mut func, &names, &Elsewhere::default()), 0);
277 assert_eq!(opcodes(&func), [Opcode::Call, Opcode::Add, Opcode::Return]);
278 }
279
280 #[test]
281 fn a_local_in_the_frame_turns_down_the_whole_function() {
282 let mut names = Interner::new();
283 let mut func = caller(&mut names, |func, block, got| {
284 Builder::new(func, block).value(InstData::new(Opcode::Alloca), Type::PTR);
285 got
286 });
287 assert_eq!(
288 refusal(&func, &names, &Elsewhere::default()),
289 Some("a local lives in the frame")
290 );
291 assert_eq!(mark(&mut func, &names, &Elsewhere::default()), 0);
292 }
293
294 #[test]
295 fn a_call_to_setjmp_anywhere_turns_down_the_whole_function() {
296 let mut names = Interner::new();
297 let setjmp = names.intern("_setjmp");
298 let mut func = caller(&mut names, |func, block, got| {
299 let sig = func.add_signature(Signature::new().with_returns(&[Type::int(32)]));
300 Builder::new(func, block).call(setjmp, sig, &[]);
301 got
302 });
303 assert_eq!(
304 refusal(&func, &names, &Elsewhere::default()),
305 Some("the function calls something that comes back twice")
306 );
307 assert_eq!(mark(&mut func, &names, &Elsewhere::default()), 0);
308 }
309
310 #[test]
311 fn glibcs_name_for_sigsetjmp_comes_back_and_an_ordinary_call_does_not() {
312 let mut names = Interner::new();
313 let setjmp = names.intern("__sigsetjmp");
314 let func = caller(&mut names, |func, block, got| {
315 let sig = func.add_signature(Signature::new().with_returns(&[Type::int(32)]));
316 Builder::new(func, block).call(setjmp, sig, &[]);
317 got
318 });
319 assert!(comes_back(&func, &names, &Elsewhere::default()));
320 let plain = caller(&mut names, |_, _, got| got);
321 assert!(!comes_back(&plain, &names, &Elsewhere::default()));
322 }
323}