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
41const TWICE: &[&str] = &["setjmp", "sigsetjmp", "savectx", "vfork", "getcontext"];
47
48#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct Tail {
52 pub call: mir::Inst,
54 pub returns: Vec<mir::Inst>,
56}
57
58#[must_use]
60pub fn refusal(func: &Func, names: &Interner) -> Option<&'static str> {
61 if func.attrs.set.contains(AttrSet::NAKED) {
62 return Some("the function is naked and writes its own ending");
63 }
64 let sret =
65 func.signature().params.first().is_some_and(|param| matches!(param.abi, Abi::Sret { .. }));
66 if sret {
67 return Some("the function gives its answer back through memory it was handed");
68 }
69 for block in func.blocks() {
70 for inst in func.insts(block) {
71 match func[inst].opcode {
72 Opcode::Alloca => return Some("a local lives in the frame"),
73 Opcode::VaStart => return Some("the function reads its own variable arguments"),
74 Opcode::ApplyArgs => return Some("the function keeps its arguments in the frame"),
75 Opcode::SetjmpMarker => return Some("the function saves a place to come back to"),
76 Opcode::Call if twice(func, inst, names) => {
77 return Some("the function calls something that comes back twice");
78 }
79 _ => (),
80 }
81 }
82 }
83 None
84}
85
86fn twice(func: &Func, inst: Inst, names: &Interner) -> bool {
88 let Extra::Call(info) = func[inst].extra else { return false };
89 let Some(callee) = func[info].callee else { return false };
90 TWICE.contains(&names.resolve(callee).trim_start_matches('_'))
91}
92
93pub fn mark(func: &mut Func, names: &Interner) -> usize {
96 if refusal(func, names).is_some() {
97 return 0;
98 }
99 let blocks: Vec<_> = func.blocks().collect();
100 let mut marked = 0;
101 for block in blocks {
102 let insts: Vec<Inst> = func.insts(block).collect();
103 let [.., call, ret] = insts[..] else { continue };
104 if !in_tail_position(func, call, ret) {
105 continue;
106 }
107 func.remove_inst(ret);
108 func[call].opcode = Opcode::TailCall;
109 marked += 1;
110 }
111 marked
112}
113
114fn in_tail_position(func: &Func, call: Inst, ret: Inst) -> bool {
121 if func[ret].opcode != Opcode::Return || func[call].opcode != Opcode::Call {
122 return false;
123 }
124 let Extra::Call(info) = func[call].extra else { return false };
125 let info = func[info];
126 if info.callee.is_none() {
127 return false;
128 }
129 let results: Vec<Value> = func[call].results().collect();
130 if func[func[ret].args] != results[..] {
131 return false;
132 }
133 func[info.signature].returns == func.signature().returns
134}
135
136pub fn jumps(
140 func: &mut mir::Func,
141 tails: &[Tail],
142 insts: &FrameInsts,
143 names: &mut Interner,
144) -> usize {
145 let Some(away) = insts.away else { return 0 };
146 let ret = mir::Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.ret)));
147 let away = mir::Opcode::new(names.intern(&format!("{}{away}", insts.prefix)));
148 let mut jumped = 0;
149 for tail in tails {
150 let Some((last, callee)) = ending(func, tail, ret) else { continue };
151 let span = func.span(tail.call);
152 func.remove_inst(tail.call);
153 for &pseudo in &tail.returns {
154 func.remove_inst(pseudo);
155 }
156 func[last].opcode = away;
159 func[last].symbol = Some(callee);
160 func.set_span(last, span);
161 jumped += 1;
162 }
163 jumped
164}
165
166fn ending(func: &mir::Func, tail: &Tail, ret: mir::Opcode) -> Option<(mir::Inst, Symbol)> {
174 let block = func.block_of(tail.call)?;
175 let callee = func[tail.call].symbol?;
176 if !func[block].succs.is_empty() {
177 return None;
178 }
179 let last = func.insts(block).last()?;
180 if func[last].opcode != ret {
181 return None;
182 }
183 let named: Vec<(mir::Reg, RegClass)> =
184 func[func[tail.call].operands].iter().map(|operand| (operand.reg, operand.class)).collect();
185 let mut at = func.next_inst(tail.call)?;
186 while at != last {
187 if !tail.returns.contains(&at) {
188 let data = &func[at];
189 let touches = func[data.operands]
190 .iter()
191 .any(|operand| named.contains(&(operand.reg, operand.class)));
192 if touches || data.symbol.is_some() {
193 return None;
194 }
195 }
196 at = func.next_inst(at)?;
197 }
198 Some((last, callee))
199}
200
201#[cfg(test)]
202mod tests {
203 use rucc_base::Interner;
204 use rucc_ir::{Block, Builder, Flags, Func, InstData, Opcode, Signature, Type, Value};
205
206 use super::{mark, refusal};
207
208 fn caller(
210 names: &mut Interner,
211 between: impl FnOnce(&mut Func, Block, Value) -> Value,
212 ) -> Func {
213 let i32 = Type::int(32);
214 let mut func =
215 Func::new(names.intern("f"), Signature::new().with_params(&[i32]).with_returns(&[i32]));
216 let block = func.create_block();
217 let arg = func.append_param(block, i32);
218 let sig = func.add_signature(Signature::new().with_params(&[i32]).with_returns(&[i32]));
219 let callee = names.intern("g");
220 let call = Builder::new(&mut func, block).call(callee, sig, &[arg]);
221 let got = func[call].first_result.expect("an integer comes back");
222 let answer = between(&mut func, block, got);
223 Builder::new(&mut func, block).ret(&[answer]);
224 func
225 }
226
227 fn opcodes(func: &Func) -> Vec<Opcode> {
228 func.blocks()
229 .flat_map(|block| func.insts(block).map(|inst| func[inst].opcode).collect::<Vec<_>>())
230 .collect()
231 }
232
233 #[test]
234 fn a_call_whose_answer_is_returned_becomes_a_tail_call() {
235 let mut names = Interner::new();
236 let mut func = caller(&mut names, |_, _, got| got);
237 assert_eq!(mark(&mut func, &names), 1);
238 assert_eq!(opcodes(&func), [Opcode::TailCall]);
239 }
240
241 #[test]
242 fn a_call_with_work_after_it_is_not_in_tail_position() {
243 let mut names = Interner::new();
244 let mut func = caller(&mut names, |func, block, got| {
245 Builder::new(func, block).binary(Opcode::Add, got, got, Flags::default())
246 });
247 assert_eq!(mark(&mut func, &names), 0);
248 assert_eq!(opcodes(&func), [Opcode::Call, Opcode::Add, Opcode::Return]);
249 }
250
251 #[test]
252 fn a_local_in_the_frame_turns_down_the_whole_function() {
253 let mut names = Interner::new();
254 let mut func = caller(&mut names, |func, block, got| {
255 Builder::new(func, block).value(InstData::new(Opcode::Alloca), Type::PTR);
256 got
257 });
258 assert_eq!(refusal(&func, &names), Some("a local lives in the frame"));
259 assert_eq!(mark(&mut func, &names), 0);
260 }
261
262 #[test]
263 fn a_call_to_setjmp_anywhere_turns_down_the_whole_function() {
264 let mut names = Interner::new();
265 let setjmp = names.intern("_setjmp");
266 let mut func = caller(&mut names, |func, block, got| {
267 let sig = func.add_signature(Signature::new().with_returns(&[Type::int(32)]));
268 Builder::new(func, block).call(setjmp, sig, &[]);
269 got
270 });
271 assert_eq!(
272 refusal(&func, &names),
273 Some("the function calls something that comes back twice")
274 );
275 assert_eq!(mark(&mut func, &names), 0);
276 }
277}