1use rucc_base::Interner;
27use rucc_ir as ir;
28use rucc_mir as mir;
29use rucc_regalloc::assign::Env;
30use rucc_target::{Arch, BranchInsts, CallRegs, FrameInsts, PhysReg, RegFile, TargetInfo, x86_64};
31
32use crate::finish::finish;
33use crate::frame::{Frame, Layout};
34use crate::layout;
35use crate::lower::{self, Unsupported};
36use crate::split;
37
38#[derive(Debug)]
47pub struct Machine {
48 pub conv: &'static CallRegs,
50 pub file: RegFile,
52 pub insts: &'static FrameInsts,
54 pub branch: &'static BranchInsts,
56 pub env: Env,
58}
59
60const SCRATCH: [PhysReg; 2] = [x86_64::R10, x86_64::R11];
67
68impl Machine {
69 #[must_use]
75 pub fn x86_64(conv: &'static CallRegs) -> Self {
76 let order: Vec<PhysReg> =
77 conv.int_order.iter().copied().filter(|reg| !SCRATCH.contains(reg)).collect();
78 Self {
79 conv,
80 file: x86_64::REGS,
81 insts: &x86_64::FRAME,
82 branch: &x86_64::BRANCH,
83 env: Env::new().with(x86_64::GPR, &order, &SCRATCH),
84 }
85 }
86
87 #[must_use]
94 pub fn for_target(target: &TargetInfo) -> Option<Self> {
95 let conv = target.call_regs?;
96 match target.triple.arch {
97 Arch::X86_64 => Some(Self::x86_64(conv)),
98 Arch::Aarch64 | Arch::Riscv64 => None,
99 }
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct Flags {
106 pub frame_pointer: bool,
108 pub red_zone: bool,
110}
111
112impl Default for Flags {
113 fn default() -> Self {
116 Self { frame_pointer: false, red_zone: true }
117 }
118}
119
120pub fn compile(
128 source: &ir::Func,
129 names: &mut Interner,
130 machine: &Machine,
131 flags: Flags,
132) -> Result<mir::Func, Unsupported> {
133 let lowered = lower::func(source, names, machine.conv)?;
134 let stack = Layout {
135 frame_pointer: flags.frame_pointer,
136 red_zone: flags.red_zone,
137 ..lowered.layout(Layout::new(machine.conv, machine.file))
138 };
139 let mut func = lowered.func;
140
141 split::critical(&mut func);
145 let allocation = rucc_regalloc::run(&mut func, &machine.env);
146
147 let frame = Frame::of(&func, &allocation, &stack);
150 finish(&mut func, &allocation, &frame, machine.conv, machine.insts, names);
151
152 layout::blocks(&mut func, machine.branch, names);
155 Ok(func)
156}
157
158#[cfg(test)]
159mod tests {
160 use rucc_ir::{Builder, Flags as IrFlags, Func, Opcode, Signature, Type};
161 use rucc_target::x86_64::{REGS, SYSV, WIN64};
162
163 use super::*;
164
165 fn blank(params: &[Type]) -> (Interner, Func, ir::Block, Vec<ir::Value>) {
167 let mut names = Interner::new();
168 let mut func = Func::new(names.intern("f"), Signature::new());
169 let block = func.create_block();
170 let values = params.iter().map(|&ty| func.append_param(block, ty)).collect();
171 (names, func, block, values)
172 }
173
174 #[test]
175 fn a_function_comes_out_with_no_virtual_register_left_in_it() {
176 let i32 = Type::int(32);
177 let (mut names, mut source, block, args) = blank(&[i32, i32]);
178 let mut build = Builder::new(&mut source, block);
179 let sum = build.binary(Opcode::Add, args[0], args[1], IrFlags::default());
180 build.ret(&[sum]);
181
182 let machine = Machine::x86_64(&SYSV);
183 let out = compile(&source, &mut names, &machine, Flags::default())
184 .expect("every instruction has a rule");
185
186 assert_eq!(
191 mir::print_func(&out, &names, ®S),
192 "mfunc @f {\n\
193 block0:\n \
194 $rdi($rdi) = x64.arg_val_32\n \
195 $rax = x64.mov_rr_64 $rdi\n \
196 $rsi($rsi) = x64.arg_val_32\n \
197 $rcx = x64.mov_rr_64 $rsi\n \
198 $rdx = x64.mov_rr_64 $rax\n \
199 $rdx(reuse 1) = x64.add_rr_32 $rax, $rcx\n \
200 $rax = x64.mov_rr_64 $rdx\n \
201 x64.ret_val_32 $rax($rax)\n \
202 x64.ret\n\
203 }\n"
204 );
205 }
206
207 #[test]
208 fn a_function_that_calls_takes_a_frame_and_gives_it_back() {
209 let i32 = Type::int(32);
210 let (mut names, mut source, block, args) = blank(&[i32]);
211 let sig = source.add_signature(Signature::new().with_params(&[i32]).with_returns(&[i32]));
212 let callee = names.intern("g");
213 let call = Builder::new(&mut source, block).call(callee, sig, &[args[0]]);
214 let got = source[call].first_result.expect("an integer comes back");
215 let mut build = Builder::new(&mut source, block);
216 let sum = build.binary(Opcode::Add, got, args[0], IrFlags::default());
217 build.ret(&[sum]);
218
219 let machine = Machine::x86_64(&SYSV);
220 let out = compile(&source, &mut names, &machine, Flags::default())
221 .expect("every instruction has a rule");
222
223 let text = mir::print_func(&out, &names, ®S);
226 assert!(text.contains("x64.push_64 $rbx"), "{text}");
227 assert!(text.contains("$rbx = x64.pop_64"), "{text}");
228 assert!(text.contains("x64.call $rdi($rdi), @g"), "{text}");
229 assert!(!text.contains('%'), "{text}");
230 }
231
232 #[test]
233 fn the_other_convention_is_the_same_function_somewhere_else() {
234 let i32 = Type::int(32);
235 let (mut names, mut source, block, args) = blank(&[i32, i32]);
236 let mut build = Builder::new(&mut source, block);
237 let sum = build.binary(Opcode::Add, args[0], args[1], IrFlags::default());
238 build.ret(&[sum]);
239
240 let machine = Machine::x86_64(&WIN64);
241 let out = compile(&source, &mut names, &machine, Flags::default())
242 .expect("every instruction has a rule");
243
244 let text = mir::print_func(&out, &names, ®S);
247 assert!(text.contains("$rcx($rcx) = x64.arg_val_32"), "{text}");
248 assert!(text.contains("$rdx($rdx) = x64.arg_val_32"), "{text}");
249 assert!(!text.contains("$rdi"), "{text}");
250 }
251
252 #[test]
253 fn a_function_with_a_branch_in_it_goes_through_every_pass() {
254 let i32 = Type::int(32);
255 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
256 let then = source.create_block();
257 let join = source.create_block();
258 let got = source.append_param(join, i32);
259 let mut build = Builder::new(&mut source, entry);
260 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
261 build.br_if(cond, then, &[], join, &[args[1]]);
262 Builder::new(&mut source, then).jump(join, &[args[0]]);
263 Builder::new(&mut source, join).ret(&[got]);
264
265 let machine = Machine::x86_64(&SYSV);
266 let out = compile(&source, &mut names, &machine, Flags::default())
267 .expect("every instruction has a rule");
268
269 assert_eq!(out.block_count(), 4);
273
274 let text = mir::print_func(&out, &names, ®S);
281 assert_eq!(
282 text,
283 "mfunc @f {\n\
284 block0:\n \
285 $rdi($rdi) = x64.arg_val_32\n \
286 $rax = x64.mov_rr_64 $rdi\n \
287 $rsi($rsi) = x64.arg_val_32\n \
288 $rcx = x64.mov_rr_64 $rsi\n \
289 $rdx = x64.cmp_set_l_32 $rax, $rcx\n \
290 x64.test_rr_8 $rdx\n \
291 x64.jcc_e block2, block1\n\
292 \nblock1:\n \
293 $rdx = x64.mov_rr_64 $rax\n \
294 x64.jmp block3\n\
295 \nblock2:\n \
296 $rdx = x64.mov_rr_64 $rcx, block3\n\
297 \nblock3:\n \
298 $rax = x64.mov_rr_64 $rdx\n \
299 x64.ret_val_32 $rax($rax)\n \
300 x64.ret\n\
301 }\n"
302 );
303 }
304
305 #[test]
311 fn a_loop_that_carries_its_values_round_keeps_all_of_them() {
312 let i32 = Type::int(32);
313 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
314 let head = source.create_block();
315 let body = source.create_block();
316 let exit = source.create_block();
317 let left = source.append_param(head, i32);
318 let right = source.append_param(head, i32);
319 Builder::new(&mut source, entry).jump(head, &[args[0], args[1]]);
320 let mut build = Builder::new(&mut source, head);
321 let zero = build.iconst(i32, 0);
322 let more = build.icmp(rucc_ir::IntPred::Ne, right, zero);
323 build.br_if(more, body, &[], exit, &[left]);
324 let mut build = Builder::new(&mut source, body);
325 let rest = build.binary(Opcode::SRem, left, right, IrFlags::default());
326 build.jump(head, &[right, rest]);
327 let result = source.append_param(exit, i32);
328 Builder::new(&mut source, exit).ret(&[result]);
329
330 let machine = Machine::x86_64(&SYSV);
331 let out = compile(&source, &mut names, &machine, Flags::default())
332 .expect("every instruction has a rule");
333
334 assert_eq!(
350 mir::print_func(&out, &names, ®S),
351 "mfunc @f {\n\
352 block0:\n \
353 $rdi($rdi) = x64.arg_val_32\n \
354 $rax = x64.mov_rr_64 $rdi\n \
355 $rsi($rsi) = x64.arg_val_32\n \
356 $rcx = x64.mov_rr_64 $rsi\n \
357 $rsi = x64.mov_rr_64 $rcx\n \
358 $rcx = x64.mov_rr_64 $rax, block1\n\
359 \nblock1:\n \
360 $rax = x64.mov_ri_32 0\n \
361 $rax = x64.cmp_set_ne_32 $rsi, $rax\n \
362 x64.test_rr_8 $rax\n \
363 x64.jcc_e block3, block2\n\
364 \nblock2:\n \
365 $rax = x64.mov_rr_64 $rcx\n \
366 $rdx($rdx), early $rax($rax) = x64.idiv_rem_32 $rax($rax), $rsi\n \
367 $rcx = x64.mov_rr_64 $rdx\n \
368 $rdi = x64.mov_rr_64 $rax\n \
369 $r10 = x64.mov_rr_64 $rsi\n \
370 $rsi = x64.mov_rr_64 $rcx\n \
371 $rcx = x64.mov_rr_64 $r10\n \
372 x64.jmp block1\n\
373 \nblock3:\n \
374 $rax = x64.mov_rr_64 $rcx\n \
375 x64.ret_val_32 $rax($rax)\n \
376 x64.ret\n\
377 }\n"
378 );
379 }
380
381 #[test]
386 fn a_function_that_has_been_laid_out_reads_back_as_the_same_function() {
387 let i32 = Type::int(32);
388 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
389 let then = source.create_block();
390 let join = source.create_block();
391 let got = source.append_param(join, i32);
392 let mut build = Builder::new(&mut source, entry);
393 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
394 build.br_if(cond, then, &[], join, &[args[1]]);
395 Builder::new(&mut source, then).jump(join, &[args[0]]);
396 Builder::new(&mut source, join).ret(&[got]);
397
398 let machine = Machine::x86_64(&SYSV);
399 let out = compile(&source, &mut names, &machine, Flags::default())
400 .expect("every instruction has a rule");
401
402 let text = mir::print_func(&out, &names, ®S);
403 let read = rucc_mir::parse(&text, &mut names, ®S).expect("what the printer wrote");
404 assert_eq!(mir::print(&read, &names, ®S), text);
405 }
406
407 #[test]
408 fn a_function_this_cannot_lower_is_reported_rather_than_compiled() {
409 let f64 = Type::float(rucc_ir::Float::F64);
410 let (mut names, mut source, block, args) = blank(&[f64]);
411 Builder::new(&mut source, block).ret(&[args[0]]);
412
413 let machine = Machine::x86_64(&SYSV);
414 let failed = compile(&source, &mut names, &machine, Flags::default())
415 .expect_err("a double arrives in a vector register");
416 assert_eq!(failed.to_string(), "parameter 0 is in a vector register");
417 }
418
419 #[test]
420 fn the_flags_reach_the_frame() {
421 let i32 = Type::int(32);
422 let (mut names, mut source, block, args) = blank(&[i32]);
423 Builder::new(&mut source, block).ret(&[args[0]]);
424
425 let machine = Machine::x86_64(&SYSV);
426 let flags = Flags { frame_pointer: true, red_zone: true };
427 let out =
428 compile(&source, &mut names, &machine, flags).expect("every instruction has a rule");
429
430 let text = mir::print_func(&out, &names, ®S);
433 assert!(text.contains("x64.push_64 $rbp"), "{text}");
434 assert!(text.contains("$rbp = x64.mov_rr_64 $rsp"), "{text}");
435 }
436
437 #[test]
438 fn a_target_says_which_machine_it_is_and_which_convention_it_uses() {
439 let triple = |text: &str| text.parse::<rucc_target::Triple>().expect("a triple");
440 let info = TargetInfo::new(triple("x86_64-unknown-linux-gnu"));
441 let machine = Machine::for_target(&info).expect("x86-64 is the target this crate covers");
442 assert!(std::ptr::eq(machine.conv, &SYSV));
443
444 let info = TargetInfo::new(triple("x86_64-pc-windows-msvc"));
445 let machine = Machine::for_target(&info).expect("x86-64 is the target this crate covers");
446 assert!(std::ptr::eq(machine.conv, &WIN64));
447
448 let info = TargetInfo::new(triple("aarch64-unknown-linux-gnu"));
451 assert!(Machine::for_target(&info).is_none());
452 }
453}