1use std::fmt;
79
80use rucc_base::Interner;
81use rucc_ir::{Block, Def, Extra, Func, Inst, Opcode, Type, Value};
82use rucc_mir as mir;
83use rucc_target::x86_64;
84use rucc_target::{CallRegs, RegClass};
85
86use crate::abi::{self, Missing, Refused};
87use crate::frame::Layout;
88use crate::select::{Match, Piece, Rule, Table};
89use crate::term::{MAX_ARGS, PLAIN, Plan, Shown, Term, Terms};
90
91const PREFIX: &str = "x64.";
94
95#[derive(Debug, Clone, PartialEq, Eq)]
100pub enum Unsupported {
101 Inst {
103 inst: Inst,
105 term: Option<&'static str>,
108 },
109 Argument {
114 index: usize,
116 missing: Missing,
118 },
119 Call {
121 inst: Inst,
123 refused: Refused,
125 },
126 Indirect {
131 inst: Inst,
133 },
134}
135
136impl fmt::Display for Unsupported {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 match *self {
139 Unsupported::Inst { term: Some(term), .. } => write!(f, "no rule lowers `{term}`"),
140 Unsupported::Inst { term: None, .. } => f.write_str("no rule lowers this instruction"),
141 Unsupported::Argument { index, missing } => {
142 write!(f, "parameter {index} {}", missing.why())
143 }
144 Unsupported::Call { refused: Refused { argument: Some(index), missing }, .. } => {
145 write!(f, "argument {index} of this call {}", missing.why())
146 }
147 Unsupported::Call { refused: Refused { argument: None, missing }, .. } => {
148 write!(f, "what this call gives back {}", missing.why())
149 }
150 Unsupported::Indirect { .. } => f.write_str("no rule calls through an address"),
151 }
152 }
153}
154
155impl std::error::Error for Unsupported {}
156
157#[derive(Debug)]
160pub struct Lowered {
161 pub func: mir::Func,
163 pub calls: Option<u32>,
171}
172
173impl Lowered {
174 #[must_use]
179 pub fn layout<'a>(&self, base: Layout<'a>) -> Layout<'a> {
180 Layout { leaf: self.calls.is_none(), outgoing: self.calls.unwrap_or(0), ..base }
181 }
182}
183
184pub fn func(
192 source: &Func,
193 names: &mut Interner,
194 conv: &'static CallRegs,
195) -> Result<Lowered, Unsupported> {
196 Lowering::new(source, names, conv).run()
197}
198
199struct Lowering<'a> {
201 source: &'a Func,
202 names: &'a mut Interner,
203 out: mir::Func,
204 regs: Vec<Option<mir::Reg>>,
206 uses: Vec<u32>,
209 at: Option<mir::Block>,
211 blocks: Vec<Option<mir::Block>>,
213 gpr: RegClass,
215 conv: &'static CallRegs,
218 calls: Option<u32>,
220}
221
222impl<'a> Lowering<'a> {
223 fn new(source: &'a Func, names: &'a mut Interner, conv: &'static CallRegs) -> Self {
224 let counts = source.counts();
225 let name = source.name;
226 let mut uses = vec![0; counts.values];
227 for block in source.blocks() {
228 for inst in source.insts(block) {
229 for &arg in &source[source[inst].args] {
230 uses[arg.index()] += 1;
231 }
232 for call in source.successors(inst) {
233 for &arg in &source[call.args] {
234 uses[arg.index()] += 1;
235 }
236 }
237 }
238 }
239 Self {
240 source,
241 names,
242 out: mir::Func::new(name),
243 regs: vec![None; counts.values],
244 blocks: vec![None; counts.blocks],
245 uses,
246 at: None,
247 gpr: x86_64::GPR,
248 conv,
249 calls: None,
250 }
251 }
252
253 fn run(mut self) -> Result<Lowered, Unsupported> {
254 for block in self.source.blocks() {
258 let out = self.out.create_block();
259 self.blocks[block.index()] = Some(out);
260 }
261 for block in self.source.blocks() {
262 self.block(block)?;
263 }
264 Ok(Lowered { func: self.out, calls: self.calls })
265 }
266
267 fn block(&mut self, block: Block) -> Result<(), Unsupported> {
269 let out = self.out_block(block);
270 self.at = Some(out);
271 if self.source.entry() == Some(block) {
272 self.arrive(block, out)?;
273 } else {
274 for ¶m in self.source[block].params.iter() {
275 let reg = self.out.append_param(out, self.gpr);
276 self.regs[param.index()] = Some(reg);
277 }
278 }
279
280 let insts: Vec<Inst> = self.source.insts(block).collect();
286 let mut found: Vec<Option<Match<Term>>> = (0..insts.len()).map(|_| None).collect();
287 let mut folded: Vec<Inst> = Vec::new();
288 for (index, &inst) in insts.iter().enumerate().rev() {
289 if folded.contains(&inst) {
290 continue;
291 }
292 if let Some((plan, matched)) = self.select(inst) {
293 folded.extend(self.folds(inst, plan));
294 found[index] = Some(matched);
295 }
296 }
297
298 for (&inst, matched) in insts.iter().zip(found) {
299 if folded.contains(&inst) || self.writes_nothing(inst) {
300 continue;
301 }
302 match self.source[inst].opcode {
307 Opcode::Call => {
308 self.called(inst)?;
309 continue;
310 }
311 Opcode::CallIndirect => return Err(Unsupported::Indirect { inst }),
312 _ => {}
313 }
314 let matched = matched.ok_or_else(|| self.unsupported(inst))?;
315 self.emit(inst, &matched)?;
316 }
317 self.edges(block, out)
318 }
319
320 fn called(&mut self, inst: Inst) -> Result<(), Unsupported> {
326 let data = &self.source[inst];
327 let Extra::Call(info) = data.extra else { return Err(self.unsupported(inst)) };
328 let info = self.source[info];
329 let Some(callee) = info.callee else { return Err(Unsupported::Indirect { inst }) };
330
331 let values: Vec<Value> = self.source[data.args].to_vec();
332 let mut args = Vec::with_capacity(values.len());
333 for value in values {
334 args.push((self.source[value].ty, self.reg_of(value)?));
335 }
336 let signature = &self.source[info.signature];
337 let variadic = signature.variadic;
338 let returns = signature.return_types().next();
339 if signature.return_types().count() > 1 {
342 return Err(self.unsupported(inst));
343 }
344
345 let block = self.at.expect("a block is being filled");
346 let what = abi::Calling { callee, args: &args, returns, variadic };
347 let made = abi::call(&mut self.out, block, &what, self.conv, self.names)
348 .map_err(|refused| Unsupported::Call { inst, refused })?;
349 self.calls = Some(self.calls.unwrap_or(0).max(made.outgoing));
350 if let (Some(result), Some(reg)) = (data.first_result, made.result) {
351 self.regs[result.index()] = Some(reg);
352 }
353 Ok(())
354 }
355
356 fn edges(&mut self, block: Block, out: mir::Block) -> Result<(), Unsupported> {
363 let Some(term) = self.source.terminator(block) else { return Ok(()) };
364 let calls: Vec<rucc_ir::BlockCall> = self.source.successors(term).collect();
365 let mut succs = Vec::with_capacity(calls.len());
366 for call in calls {
367 let args: Vec<Value> = self.source[call.args].to_vec();
368 let mut regs = Vec::with_capacity(args.len());
369 for value in args {
370 regs.push(self.reg_of(value)?);
371 }
372 succs.push(mir::BlockCall { block: self.out_block(call.block), args: regs });
373 }
374 *self.out.succs_mut(out) = succs;
375 Ok(())
376 }
377
378 fn out_block(&self, block: Block) -> mir::Block {
380 self.blocks[block.index()].expect("every block was created before any was filled")
381 }
382
383 fn arrive(&mut self, block: Block, out: mir::Block) -> Result<(), Unsupported> {
390 let params = self.source[block].params.clone();
391 let types: Vec<Type> = params.iter().map(|&value| self.source[value].ty).collect();
392 let regs = abi::entry(&mut self.out, out, &types, self.conv, self.names)
393 .map_err(|(index, missing)| Unsupported::Argument { index, missing })?;
394 for (¶m, reg) in params.iter().zip(regs) {
395 self.regs[param.index()] = Some(reg);
396 }
397 Ok(())
398 }
399
400 fn writes_nothing(&self, inst: Inst) -> bool {
412 let data = &self.source[inst];
413 match data.opcode {
414 Opcode::IConst | Opcode::Jump => true,
415 Opcode::Return => self.source[data.args].is_empty(),
416 _ => false,
417 }
418 }
419
420 fn select(&self, inst: Inst) -> Option<(Plan, Match<Term>)> {
426 for plan in self.plans(inst) {
427 let terms = Terms::new(self.source, inst, plan);
428 if let Some(matched) = TABLE.find(&terms, Term::Root) {
429 return Some((plan, matched));
430 }
431 }
432 None
433 }
434
435 fn plans(&self, inst: Inst) -> Vec<Plan> {
437 let args = &self.source[self.source[inst].args];
438 let mut plans = vec![PLAIN];
439 for (index, &arg) in args.iter().enumerate().take(MAX_ARGS) {
440 let mut ways = Vec::new();
441 if self.foldable(inst, arg) {
442 ways.push(Shown::Expand);
443 }
444 if Terms::new(self.source, inst, PLAIN).constant(arg).is_some() {
445 ways.push(Shown::Const);
446 }
447 ways.push(Shown::Reg);
448 plans = plans
449 .into_iter()
450 .flat_map(|plan| {
451 ways.iter().map(move |&way| {
452 let mut next = plan;
453 next[index] = way;
454 next
455 })
456 })
457 .collect();
458 }
459 plans
460 }
461
462 fn foldable(&self, into: Inst, value: Value) -> bool {
470 let Def::Result { inst, .. } = self.source[value].def else { return false };
471 if self.source[inst].opcode == Opcode::IConst || self.uses[value.index()] != 1 {
472 return false;
473 }
474 self.source.block_of(inst).is_some()
475 && self.source.block_of(inst) == self.source.block_of(into)
476 }
477
478 fn folds(&self, inst: Inst, plan: Plan) -> Vec<Inst> {
485 let args = &self.source[self.source[inst].args];
486 args.iter()
487 .take(MAX_ARGS)
488 .enumerate()
489 .filter(|&(index, _)| plan[index] == Shown::Expand)
490 .filter_map(|(_, &arg)| match self.source[arg].def {
491 Def::Result { inst, .. } => Some(inst),
492 Def::Param { .. } => None,
493 })
494 .collect()
495 }
496
497 fn emit(&mut self, inst: Inst, matched: &Match<Term>) -> Result<(), Unsupported> {
499 let rule: &Rule = TABLE.rule(matched);
500 let pieces = rule.replacement;
501 let Some(Piece::App { head, arity }) = pieces.first() else {
502 return Err(self.unsupported(inst));
503 };
504 let opcode = head.strip_prefix(PREFIX).ok_or_else(|| self.unsupported(inst))?;
505 let form = x86_64::form(opcode).ok_or_else(|| self.unsupported(inst))?;
506
507 let mut read = Read::default();
508 let mut at = 1;
509 for _ in 0..*arity {
510 at = self.read(inst, pieces, at, &matched.bindings, &mut read)?;
511 }
512
513 let descs = form.operands();
514 let writes = descs.iter().take_while(|desc| desc.role.is_def()).count();
515 if descs.len() - writes != read.regs.len() {
516 return Err(self.unsupported(inst));
517 }
518
519 let mut regs = Vec::new();
525 if writes > 0 {
526 let result = self.source[inst].first_result.ok_or_else(|| self.unsupported(inst))?;
527 regs.push(self.new_reg(result));
528 regs.extend((1..writes).map(|_| self.out.new_vreg(self.gpr)));
529 } else if self.source[inst].first_result.is_some() {
530 return Err(self.unsupported(inst));
533 }
534 regs.extend(read.regs.iter().copied());
535
536 let block = self.at.expect("a block is being filled");
537 let opcode = mir::Opcode::new(self.names.intern(head));
538 let mut build = self.out.build(block, opcode).at(self.source.span(inst));
539 for (desc, reg) in descs.iter().zip(regs) {
540 let operand = mir::Operand {
541 reg,
542 class: desc.class,
543 role: desc.role,
544 constraint: desc.constraint,
545 };
546 build = build.operand(operand);
547 }
548 if let Some(mem) = read.mem {
549 build = build.mem(mem);
550 }
551 if let Some(imm) = read.imm {
552 build = build.imm(imm);
553 }
554 build.finish();
555 Ok(())
556 }
557
558 fn read(
563 &mut self,
564 inst: Inst,
565 pieces: &'static [Piece],
566 at: usize,
567 bindings: &[Term],
568 out: &mut Read,
569 ) -> Result<usize, Unsupported> {
570 match pieces.get(at) {
571 Some(Piece::Int(value)) => {
572 out.imm = i64::try_from(*value).ok();
573 Ok(at + 1)
574 }
575 Some(Piece::Var { index, .. }) => {
576 match bindings.get(*index) {
577 Some(&Term::Reg(value)) => {
578 let reg = self.reg_of(value)?;
579 out.regs.push(reg);
580 }
581 Some(&Term::Num(value)) => out.imm = i64::try_from(value).ok(),
582 _ => return Err(self.unsupported(inst)),
585 }
586 Ok(at + 1)
587 }
588 Some(Piece::App { head, arity }) => {
589 let kind = x86_64::address(head).ok_or_else(|| self.unsupported(inst))?;
590 let mut inner = Read::default();
591 let mut next = at + 1;
592 for _ in 0..*arity {
593 next = self.read(inst, pieces, next, bindings, &mut inner)?;
594 }
595 let mem = address(kind, &inner, self.gpr).ok_or_else(|| self.unsupported(inst))?;
596 out.mem = Some(mem);
597 Ok(next)
598 }
599 None => Err(self.unsupported(inst)),
600 }
601 }
602
603 fn reg_of(&mut self, value: Value) -> Result<mir::Reg, Unsupported> {
606 if let Some(reg) = self.regs[value.index()] {
607 return Ok(reg);
608 }
609 let constant = match self.source[value].def {
610 Def::Result { inst, .. } => {
611 (self.source[inst].opcode == Opcode::IConst).then_some(inst)
612 }
613 Def::Param { .. } => None,
614 };
615 if let Some(inst) = constant {
616 let matched = self
617 .select(inst)
618 .map(|(_, matched)| matched)
619 .ok_or_else(|| self.unsupported(inst))?;
620 self.emit(inst, &matched)?;
621 return Ok(self.regs[value.index()].expect("a constant is written into a register"));
622 }
623 Ok(self.new_reg(value))
624 }
625
626 fn new_reg(&mut self, value: Value) -> mir::Reg {
628 if let Some(reg) = self.regs[value.index()] {
629 return reg;
630 }
631 let reg = self.out.new_vreg(self.gpr);
632 self.regs[value.index()] = Some(reg);
633 reg
634 }
635
636 fn unsupported(&self, inst: Inst) -> Unsupported {
637 Unsupported::Inst { inst, term: Terms::new(self.source, inst, PLAIN).name(inst) }
638 }
639}
640
641#[derive(Debug, Default)]
643struct Read {
644 regs: Vec<mir::Reg>,
645 imm: Option<i64>,
646 mem: Option<mir::Mem>,
647}
648
649fn address(kind: x86_64::Address, read: &Read, gpr: RegClass) -> Option<mir::Mem> {
655 let mut regs = read.regs.iter().copied().map(|reg| mir::Operand::read(reg, gpr));
656 match kind {
657 x86_64::Address::BaseIndexScale => {
658 let base = regs.next()?;
659 let index = regs.next()?;
660 Some(mir::Mem::at(base).indexed(index, u8::try_from(read.imm?).ok()?))
661 }
662 x86_64::Address::IndexScale => Some(mir::Mem {
663 base: None,
664 index: Some(regs.next()?),
665 scale: u8::try_from(read.imm?).ok()?,
666 disp: 0,
667 symbol: None,
668 }),
669 x86_64::Address::Base => Some(mir::Mem::at(regs.next()?)),
670 x86_64::Address::BaseOffset => {
673 Some(mir::Mem { disp: i32::try_from(read.imm?).ok()?, ..mir::Mem::at(regs.next()?) })
674 }
675 }
676}
677
678static TABLE: &Table = &crate::select::x86_64::TABLE;
684
685#[cfg(test)]
686mod tests {
687 use rucc_ir::{Builder, CallInfo, Flags, InstData, MemInfo, MemOrder, Signature, Type};
688 use rucc_regalloc::assign::Env;
689 use rucc_target::x86_64::{FRAME, REGS, SYSV};
690
691 use super::*;
692 use crate::finish::finish;
693 use crate::frame::{Frame, Layout};
694
695 fn blank(params: &[Type]) -> (Interner, Func, Block, Vec<Value>) {
697 let mut names = Interner::new();
698 let mut func = Func::new(names.intern("f"), Signature::new());
699 let block = func.create_block();
700 let values = params.iter().map(|&ty| func.append_param(block, ty)).collect();
701 (names, func, block, values)
702 }
703
704 fn plain() -> MemInfo {
707 MemInfo { size: 0, align: 1, order: MemOrder::NotAtomic, tbaa: None }
708 }
709
710 fn env() -> Env {
715 const SCRATCH: [rucc_target::PhysReg; 2] = [x86_64::R10, x86_64::R11];
716 let order: Vec<rucc_target::PhysReg> =
717 SYSV.int_order.iter().copied().filter(|reg| !SCRATCH.contains(reg)).collect();
718 Env::new().with(x86_64::GPR, &order, &SCRATCH)
719 }
720
721 fn lower(names: &mut Interner, source: &Func) -> String {
723 let out = func(source, names, &SYSV).expect("every instruction has a rule");
724 mir::print_func(&out.func, names, ®S)
725 }
726
727 #[test]
728 fn an_addition_of_two_registers_is_one_instruction() {
729 let i32 = Type::int(32);
730 let (mut names, mut func, block, args) = blank(&[i32, i32]);
731 let mut build = Builder::new(&mut func, block);
732 build.binary(Opcode::Add, args[0], args[1], Flags::default());
733
734 assert_eq!(
735 lower(&mut names, &func),
736 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
737 %1:gpr($rsi) = x64.arg_val_32\n %2:gpr(reuse 1) = x64.add_rr_32 %0, %1\n}\n"
738 );
739 }
740
741 #[test]
742 fn a_constant_operand_becomes_an_immediate() {
743 let i32 = Type::int(32);
744 let (mut names, mut func, block, args) = blank(&[i32]);
745 let mut build = Builder::new(&mut func, block);
746 let seven = build.iconst(i32, 7);
747 build.binary(Opcode::Add, args[0], seven, Flags::default());
748
749 assert_eq!(
752 lower(&mut names, &func),
753 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
754 %1:gpr(reuse 1) = x64.add_ri_32 %0, 7\n}\n"
755 );
756 }
757
758 #[test]
759 fn a_constant_too_wide_for_an_immediate_goes_into_a_register() {
760 let i64 = Type::int(64);
761 let (mut names, mut func, block, args) = blank(&[i64]);
762 let mut build = Builder::new(&mut func, block);
763 let big = build.iconst(i64, i128::from(i32::MAX) + 1);
764 build.binary(Opcode::Add, args[0], big, Flags::default());
765
766 assert_eq!(
770 lower(&mut names, &func),
771 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
772 %1:gpr = x64.mov_ri_64 2147483648\n %2:gpr(reuse 1) = x64.add_rr_64 %0, %1\n}\n"
773 );
774 }
775
776 #[test]
777 fn an_index_calculation_folds_into_an_address() {
778 let i64 = Type::int(64);
779 let (mut names, mut func, block, args) = blank(&[i64, i64]);
780 let mut build = Builder::new(&mut func, block);
781 let four = build.iconst(i64, 4);
782 let scaled = build.binary(Opcode::Mul, args[1], four, Flags::default());
783 build.binary(Opcode::Add, args[0], scaled, Flags::default());
784
785 assert_eq!(
788 lower(&mut names, &func),
789 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
790 %1:gpr($rsi) = x64.arg_val_64\n %2:gpr = x64.lea_64 [%0 + %1*4]\n}\n"
791 );
792 }
793
794 #[test]
795 fn an_instruction_read_twice_is_not_folded_into_either_reader() {
796 let i64 = Type::int(64);
797 let (mut names, mut func, block, args) = blank(&[i64, i64]);
798 let mut build = Builder::new(&mut func, block);
799 let four = build.iconst(i64, 4);
800 let scaled = build.binary(Opcode::Mul, args[1], four, Flags::default());
801 let first = build.binary(Opcode::Add, args[0], scaled, Flags::default());
802 build.binary(Opcode::Add, first, scaled, Flags::default());
803
804 let text = lower(&mut names, &func);
807 assert!(text.contains("x64.lea_64 [%1*4]"), "{text}");
808 assert_eq!(text.matches("x64.add_rr_64").count(), 2, "{text}");
809 }
810
811 #[test]
812 fn a_shift_by_a_register_asks_for_it_in_cl() {
813 let i32 = Type::int(32);
814 let (mut names, mut func, block, args) = blank(&[i32, i32]);
815 let mut build = Builder::new(&mut func, block);
816 build.binary(Opcode::Shl, args[0], args[1], Flags::default());
817
818 let text = lower(&mut names, &func);
821 assert!(text.contains("x64.shl_rcl_32 %0, %1($rcx)"), "{text}");
822 }
823
824 #[test]
825 fn a_division_names_the_registers_and_the_register_it_destroys() {
826 let i32 = Type::int(32);
827 let (mut names, mut func, block, args) = blank(&[i32, i32]);
828 let mut build = Builder::new(&mut func, block);
829 build.binary(Opcode::SDiv, args[0], args[1], Flags::default());
830
831 let text = lower(&mut names, &func);
834 assert!(
835 text.contains("%2:gpr($rax), early %3:gpr($rdx) = x64.idiv_quo_32 %0($rax), %1"),
836 "{text}"
837 );
838 }
839
840 #[test]
841 fn a_load_reads_through_the_register_the_address_is_in() {
842 let i64 = Type::int(64);
843 let (mut names, mut func, block, args) = blank(&[i64]);
844 let mut build = Builder::new(&mut func, block);
845 build.load(Type::int(32), args[0], plain(), Flags::default());
846
847 assert_eq!(
848 lower(&mut names, &func),
849 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
850 %1:gpr = x64.mov_rm_32 [%0]\n}\n"
851 );
852 }
853
854 #[test]
855 fn a_store_writes_no_register_and_the_value_it_writes_is_the_one_the_ir_gave_it() {
856 let (mut names, mut func, block, args) = blank(&[Type::int(32), Type::int(64)]);
857 let mut build = Builder::new(&mut func, block);
858 build.store(args[0], args[1], plain(), Flags::default());
859
860 assert_eq!(
864 lower(&mut names, &func),
865 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
866 %1:gpr($rsi) = x64.arg_val_64\n x64.mov_mr_32 %0, [%1]\n}\n"
867 );
868 }
869
870 #[test]
871 fn an_address_with_a_constant_added_folds_into_the_access() {
872 let i64 = Type::int(64);
873 let (mut names, mut func, block, args) = blank(&[i64]);
874 let mut build = Builder::new(&mut func, block);
875 let twelve = build.iconst(i64, 12);
876 let field = build.binary(Opcode::Add, args[0], twelve, Flags::default());
877 build.load(Type::int(64), field, plain(), Flags::default());
878
879 assert_eq!(
882 lower(&mut names, &func),
883 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
884 %1:gpr = x64.mov_rm_64 [%0 + 12]\n}\n"
885 );
886 }
887
888 #[test]
889 fn a_displacement_too_wide_to_encode_leaves_the_addition_where_it_is() {
890 let i64 = Type::int(64);
891 let (mut names, mut func, block, args) = blank(&[i64]);
892 let mut build = Builder::new(&mut func, block);
893 let big = build.iconst(i64, i128::from(i32::MAX) + 1);
894 let far = build.binary(Opcode::Add, args[0], big, Flags::default());
895 build.load(Type::int(32), far, plain(), Flags::default());
896
897 let text = lower(&mut names, &func);
901 assert!(text.contains("x64.mov_rm_32 [%2]"), "{text}");
902 assert!(text.contains("x64.add_rr_64"), "{text}");
903 }
904
905 #[test]
906 fn a_store_of_a_value_that_was_loaded_is_two_instructions_and_no_arithmetic() {
907 let i64 = Type::int(64);
908 let (mut names, mut func, block, args) = blank(&[i64, i64]);
909 let mut build = Builder::new(&mut func, block);
910 let got = build.load(Type::int(8), args[0], plain(), Flags::default());
911 build.store(got, args[1], plain(), Flags::default());
912
913 assert_eq!(
917 lower(&mut names, &func),
918 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
919 %1:gpr($rsi) = x64.arg_val_64\n %2:gpr = x64.mov_rm_8 [%0]\n \
920 x64.mov_mr_8 %2, [%1]\n}\n"
921 );
922 }
923
924 #[test]
925 fn an_access_at_a_width_no_rule_is_written_at_is_reported() {
926 let i64 = Type::int(64);
927 let (mut names, mut source, block, args) = blank(&[i64]);
928 let mut build = Builder::new(&mut source, block);
929 build.load(Type::int(128), args[0], plain(), Flags::default());
930
931 let failed = func(&source, &mut names, &SYSV).expect_err("nothing loads 128 bits");
932 assert_eq!(failed.to_string(), "no rule lowers this instruction");
933 }
934
935 #[test]
936 fn a_return_asks_for_the_value_in_the_register_the_caller_reads() {
937 let (mut names, mut func, block, args) = blank(&[Type::int(32)]);
938 let mut build = Builder::new(&mut func, block);
939 build.ret(&[args[0]]);
940
941 assert_eq!(
946 lower(&mut names, &func),
947 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
948 x64.ret_val_32 %0($rax)\n}\n"
949 );
950 }
951
952 #[test]
953 fn a_return_of_a_constant_puts_it_in_a_register_first() {
954 let (mut names, mut func, block, _) = blank(&[]);
955 let mut build = Builder::new(&mut func, block);
956 let zero = build.iconst(Type::int(32), 0);
957 build.ret(&[zero]);
958
959 assert_eq!(
963 lower(&mut names, &func),
964 "mfunc @f {\nblock0:\n %0:gpr = x64.mov_ri_32 0\n x64.ret_val_32 %0($rax)\n}\n"
965 );
966 }
967
968 #[test]
969 fn a_return_of_nothing_is_no_instruction_at_all() {
970 let (mut names, mut func, block, _) = blank(&[]);
971 let mut build = Builder::new(&mut func, block);
972 build.ret(&[]);
973
974 assert_eq!(lower(&mut names, &func), "mfunc @f {\nblock0:\n}\n");
978 }
979
980 #[test]
981 fn the_allocator_is_what_moves_the_answer_into_the_return_register() {
982 let (mut names, mut source, block, _) = blank(&[]);
983 let mut build = Builder::new(&mut source, block);
984 let zero = build.iconst(Type::int(32), 0);
985 build.ret(&[zero]);
986
987 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
988 let env = env();
989 let allocation = rucc_regalloc::run(&mut out, &env);
990 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
991 finish(&mut out, &allocation, &frame, &SYSV, &FRAME, &mut names);
992
993 assert_eq!(
1004 mir::print_func(&out, &names, ®S),
1005 "mfunc @f {\nblock0:\n $rcx = x64.mov_ri_32 0\n $rax = x64.mov_rr_64 $rcx\n \
1006 x64.ret_val_32 $rax($rax)\n x64.ret\n}\n"
1007 );
1008 }
1009
1010 #[test]
1011 fn a_function_of_two_arguments_is_a_whole_function_now() {
1012 let i32 = Type::int(32);
1013 let (mut names, mut source, block, args) = blank(&[i32, i32]);
1014 let mut build = Builder::new(&mut source, block);
1015 let sum = build.binary(Opcode::Add, args[0], args[1], Flags::default());
1016 build.ret(&[sum]);
1017
1018 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1019 let env = env();
1020 let allocation = rucc_regalloc::run(&mut out, &env);
1021 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
1022 finish(&mut out, &allocation, &frame, &SYSV, &FRAME, &mut names);
1023
1024 assert_eq!(
1040 mir::print_func(&out, &names, ®S),
1041 "mfunc @f {\nblock0:\n $rdi($rdi) = x64.arg_val_32\n \
1042 $rax = x64.mov_rr_64 $rdi\n $rsi($rsi) = x64.arg_val_32\n \
1043 $rcx = x64.mov_rr_64 $rsi\n $rdx = x64.mov_rr_64 $rax\n \
1044 $rdx(reuse 1) = x64.add_rr_32 $rax, $rcx\n $rax = x64.mov_rr_64 $rdx\n \
1045 x64.ret_val_32 $rax($rax)\n x64.ret\n}\n"
1046 );
1047 }
1048
1049 #[test]
1050 fn an_argument_with_no_register_left_for_it_is_reported() {
1051 let i64 = Type::int(64);
1052 let (mut names, mut source, block, args) = blank(&[i64; 7]);
1053 let mut build = Builder::new(&mut source, block);
1054 build.ret(&[args[6]]);
1055
1056 let failed = func(&source, &mut names, &SYSV).expect_err("the seventh is on the stack");
1060 assert_eq!(failed.to_string(), "parameter 6 is passed on the stack");
1061 }
1062
1063 #[test]
1064 fn a_jump_is_the_edge_and_nothing_else() {
1065 let i32 = Type::int(32);
1066 let (mut names, mut source, entry, args) = blank(&[i32]);
1067 let next = source.create_block();
1068 let got = source.append_param(next, i32);
1069 Builder::new(&mut source, entry).jump(next, &[args[0]]);
1070 Builder::new(&mut source, next).ret(&[got]);
1071
1072 assert_eq!(
1075 lower(&mut names, &source),
1076 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32 block1(%0)\n\n\
1077 block1(%1:gpr):\n x64.ret_val_32 %1($rax)\n}\n"
1078 );
1079 }
1080
1081 #[test]
1082 fn a_conditional_branch_is_lowered_to_the_condition_and_nothing_about_where_it_goes() {
1083 let i32 = Type::int(32);
1084 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1085 let then = source.create_block();
1086 let other = source.create_block();
1087 let mut build = Builder::new(&mut source, entry);
1088 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1089 build.br_if(cond, then, &[], other, &[]);
1090 Builder::new(&mut source, then).ret(&[args[0]]);
1091 Builder::new(&mut source, other).ret(&[args[1]]);
1092
1093 assert_eq!(
1097 lower(&mut names, &source),
1098 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
1099 %1:gpr($rsi) = x64.arg_val_32\n %2:gpr = x64.cmp_set_l_32 %0, %1\n \
1100 x64.br_cond_8 %2, block1, block2\n\n\
1101 block1:\n x64.ret_val_32 %0($rax)\n\n\
1102 block2:\n x64.ret_val_32 %1($rax)\n}\n"
1103 );
1104 }
1105
1106 #[test]
1107 fn a_branch_over_a_block_is_a_whole_function_now() {
1108 let i32 = Type::int(32);
1109 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1110 let then = source.create_block();
1111 let other = source.create_block();
1112 let join = source.create_block();
1113 let got = source.append_param(join, i32);
1114 let mut build = Builder::new(&mut source, entry);
1115 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1116 build.br_if(cond, then, &[], other, &[]);
1117 let mut build = Builder::new(&mut source, then);
1118 let sum = build.binary(Opcode::Add, args[0], args[1], Flags::default());
1119 build.jump(join, &[sum]);
1120 Builder::new(&mut source, other).jump(join, &[args[1]]);
1121 Builder::new(&mut source, join).ret(&[got]);
1122
1123 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1129 assert_eq!(crate::split::critical(&mut out), 0, "no edge here is critical");
1130 let env = env();
1131 let allocation = rucc_regalloc::run(&mut out, &env);
1132 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
1133 finish(&mut out, &allocation, &frame, &SYSV, &FRAME, &mut names);
1134
1135 let text = mir::print_func(&out, &names, ®S);
1140 assert_eq!(text.matches("x64.ret\n").count(), 1, "{text}");
1141 assert!(text.contains("x64.br_cond_8"), "{text}");
1142 assert!(text.contains("x64.add_rr_32"), "{text}");
1143 assert!(!text.contains('%'), "{text}");
1144 }
1145
1146 #[test]
1147 fn a_critical_edge_is_split_before_the_allocator_ever_sees_it() {
1148 let i32 = Type::int(32);
1149 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1150 let then = source.create_block();
1151 let join = source.create_block();
1152 let got = source.append_param(join, i32);
1153 let mut build = Builder::new(&mut source, entry);
1154 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1155 build.br_if(cond, then, &[], join, &[args[1]]);
1156 Builder::new(&mut source, then).jump(join, &[args[0]]);
1157 let mut build = Builder::new(&mut source, join);
1158 let twice = build.binary(Opcode::Add, got, got, Flags::default());
1159 build.ret(&[twice]);
1160
1161 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1166 assert_eq!(crate::split::critical(&mut out), 1);
1167 let env = env();
1168 let allocation = rucc_regalloc::run(&mut out, &env);
1169 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
1170 finish(&mut out, &allocation, &frame, &SYSV, &FRAME, &mut names);
1171
1172 let text = mir::print_func(&out, &names, ®S);
1174 assert_eq!(out.block_count(), 4, "{text}");
1175 assert_eq!(text.matches("x64.ret\n").count(), 1, "{text}");
1176 }
1177
1178 #[test]
1179 fn a_call_passes_what_the_convention_says_and_takes_back_what_it_says() {
1180 let i32 = Type::int(32);
1181 let (mut names, mut source, block, args) = blank(&[i32, i32]);
1182 let sig =
1183 source.add_signature(Signature::new().with_params(&[i32, i32]).with_returns(&[i32]));
1184 let callee = names.intern("g");
1185 let call = Builder::new(&mut source, block).call(callee, sig, &[args[0], args[1]]);
1186 let got = source[call].first_result.expect("an integer comes back");
1187 Builder::new(&mut source, block).ret(&[got]);
1188
1189 let text = lower(&mut names, &source);
1193 assert!(text.contains("= x64.call %0($rdi), %1($rsi), @g"), "{text}");
1194 assert!(text.contains("x64.ret_val_32 %2($rax)"), "{text}");
1195 assert!(text.contains("%2:gpr($rax), $rcx, $rdx, $r8, $r9, $r10, $r11, $xmm0,"), "{text}");
1199 assert!(text.contains("$xmm15 = x64.call"), "{text}");
1200 }
1201
1202 #[test]
1203 fn what_the_frame_owes_a_call_comes_back_with_the_function() {
1204 let i32 = Type::int(32);
1205 let sig = |source: &mut Func| source.add_signature(Signature::new().with_params(&[i32]));
1206
1207 let (mut names, mut source, block, args) = blank(&[i32]);
1208 let sig = sig(&mut source);
1209 let callee = names.intern("g");
1210 Builder::new(&mut source, block).call(callee, sig, &[args[0]]);
1211 let out = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1212
1213 assert_eq!(out.calls, Some(0));
1216 let layout = out.layout(Layout::new(&SYSV, REGS));
1217 assert!(!layout.leaf);
1218 assert_eq!(layout.outgoing, 0);
1219
1220 let out = func(&source, &mut names, &x86_64::WIN64).expect("every instruction has a rule");
1223 assert_eq!(out.calls, Some(32));
1224
1225 let (mut names, mut source, block, args) = blank(&[i32]);
1227 Builder::new(&mut source, block).ret(&[args[0]]);
1228 let out = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1229 assert_eq!(out.calls, None);
1230 assert!(out.layout(Layout::new(&SYSV, REGS)).leaf);
1231 }
1232
1233 #[test]
1234 fn a_value_that_outlives_a_call_is_not_left_where_the_call_destroys_it() {
1235 let i32 = Type::int(32);
1236 let (mut names, mut source, block, args) = blank(&[i32]);
1237 let sig = source.add_signature(Signature::new().with_params(&[i32]).with_returns(&[i32]));
1238 let callee = names.intern("g");
1239 let call = Builder::new(&mut source, block).call(callee, sig, &[args[0]]);
1240 let got = source[call].first_result.expect("an integer comes back");
1241 let mut build = Builder::new(&mut source, block);
1242 let sum = build.binary(Opcode::Add, got, args[0], Flags::default());
1243 build.ret(&[sum]);
1244
1245 let lowered = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1248 let layout = lowered.layout(Layout::new(&SYSV, REGS));
1249 let mut out = lowered.func;
1250 let env = env();
1251 let allocation = rucc_regalloc::run(&mut out, &env);
1252 let frame = Frame::of(&out, &allocation, &layout);
1253 finish(&mut out, &allocation, &frame, &SYSV, &FRAME, &mut names);
1254
1255 let text = mir::print_func(&out, &names, ®S);
1258 assert!(text.contains("$rbx"), "{text}");
1259 assert!(!text.contains('%'), "{text}");
1260 assert_eq!(text.matches("x64.call").count(), 1, "{text}");
1261 }
1262
1263 #[test]
1264 fn a_call_this_cannot_make_is_reported_rather_than_made() {
1265 let i64 = Type::int(64);
1266 let (mut names, mut source, block, args) = blank(&[i64]);
1267 let seven = vec![i64; 7];
1268 let sig = source.add_signature(Signature::new().with_params(&seven));
1269 let callee = names.intern("g");
1270 let passed = vec![args[0]; 7];
1271 Builder::new(&mut source, block).call(callee, sig, &passed);
1272
1273 let failed = func(&source, &mut names, &SYSV).expect_err("the seventh is on the stack");
1276 assert_eq!(failed.to_string(), "argument 6 of this call is passed on the stack");
1277
1278 let (mut names, mut source, block, _) = blank(&[]);
1279 let sig = source
1280 .add_signature(Signature::new().with_returns(&[Type::float(rucc_ir::Float::F64)]));
1281 let callee = names.intern("g");
1282 Builder::new(&mut source, block).call(callee, sig, &[]);
1283 let failed = func(&source, &mut names, &SYSV).expect_err("a double comes back in xmm0");
1284 assert_eq!(failed.to_string(), "what this call gives back is in a vector register");
1285 }
1286
1287 #[test]
1288 fn a_call_through_an_address_is_reported_as_one() {
1289 let i32 = Type::int(32);
1290 let (mut names, mut source, block, args) = blank(&[i32]);
1291 let sig = source.add_signature(Signature::new().with_params(&[i32]));
1292 let varargs = source.push_abis(&[]);
1293 let info = source.add_call(CallInfo { callee: None, signature: sig, varargs });
1294 let mut build = Builder::new(&mut source, block);
1295 let inst = InstData {
1296 args: build.func().push_values(&[args[0], args[0]]),
1297 extra: Extra::Call(info),
1298 ..InstData::new(Opcode::CallIndirect)
1299 };
1300 build.inst(inst, &[]);
1301
1302 let failed = func(&source, &mut names, &SYSV).expect_err("nothing calls through a value");
1305 assert_eq!(failed.to_string(), "no rule calls through an address");
1306 }
1307
1308 #[test]
1309 fn an_instruction_no_rule_covers_is_reported() {
1310 let i64 = Type::int(64);
1311 let (mut names, mut source, block, args) = blank(&[i64, i64]);
1312 let mut build = Builder::new(&mut source, block);
1313 build.ret(&[args[0], args[1]]);
1314
1315 let failed = func(&source, &mut names, &SYSV).expect_err("nothing returns two values");
1318 assert_eq!(failed.to_string(), "no rule lowers this instruction");
1319 }
1320}