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, Local};
88use crate::select::{Match, Piece, Rule, Table};
89use crate::term::{MAX_ARGS, PLAIN, Plan, Shown, Term, Terms};
90
91const PREFIX: &str = "x64.";
94
95const ADDRESS_BITS: u32 = 64;
98
99#[derive(Debug, Clone, PartialEq, Eq)]
104pub enum Unsupported {
105 Inst {
107 inst: Inst,
109 term: Option<&'static str>,
112 opcode: Opcode,
117 ty: Option<Type>,
119 },
120 Argument {
125 index: usize,
127 missing: Missing,
129 },
130 Call {
132 inst: Inst,
134 refused: Refused,
136 },
137 Indirect {
142 inst: Inst,
144 },
145 Dynamic {
153 inst: Inst,
155 },
156}
157
158impl Unsupported {
159 pub fn inst(&self) -> Option<Inst> {
165 match *self {
166 Unsupported::Inst { inst, .. }
167 | Unsupported::Call { inst, .. }
168 | Unsupported::Indirect { inst, .. }
169 | Unsupported::Dynamic { inst, .. } => Some(inst),
170 Unsupported::Argument { .. } => None,
171 }
172 }
173}
174
175impl fmt::Display for Unsupported {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 match *self {
178 Unsupported::Inst { term: Some(term), .. } => write!(f, "no rule lowers `{term}`"),
179 Unsupported::Inst { term: None, opcode, ty: Some(ty), .. } => {
180 write!(f, "no rule lowers a `{opcode}` producing a `{ty}`")
181 }
182 Unsupported::Inst { term: None, opcode, ty: None, .. } => {
183 write!(f, "no rule lowers a `{opcode}`")
184 }
185 Unsupported::Argument { index, missing } => {
186 write!(f, "parameter {index} {}", missing.why())
187 }
188 Unsupported::Call { refused: Refused { argument: Some(index), missing }, .. } => {
189 write!(f, "argument {index} of this call {}", missing.why())
190 }
191 Unsupported::Call { refused: Refused { argument: None, missing }, .. } => {
192 write!(f, "what this call gives back {}", missing.why())
193 }
194 Unsupported::Indirect { .. } => f.write_str("no rule calls through an address"),
195 Unsupported::Dynamic { .. } => {
196 f.write_str("nothing here grows the stack for a variable length array")
197 }
198 }
199 }
200}
201
202impl std::error::Error for Unsupported {}
203
204#[derive(Debug)]
206pub struct Lowered {
207 pub func: mir::Func,
209 pub stack: Stack,
212}
213
214#[derive(Debug, Default)]
219pub struct Stack {
220 pub calls: Option<u32>,
226 pub locals: Vec<Local>,
229 pub addresses: Vec<(mir::Inst, usize)>,
235}
236
237impl Stack {
238 #[must_use]
243 pub fn layout<'a>(&'a self, base: Layout<'a>) -> Layout<'a> {
244 Layout {
245 leaf: self.calls.is_none(),
246 outgoing: self.calls.unwrap_or(0),
247 locals: &self.locals,
248 ..base
249 }
250 }
251}
252
253pub fn func(
261 source: &Func,
262 names: &mut Interner,
263 conv: &'static CallRegs,
264) -> Result<Lowered, Unsupported> {
265 Lowering::new(source, names, conv).run()
266}
267
268struct Lowering<'a> {
270 source: &'a Func,
271 names: &'a mut Interner,
272 out: mir::Func,
273 regs: Vec<Option<mir::Reg>>,
275 written: Vec<Option<mir::Block>>,
278 uses: Vec<u32>,
281 at: Option<mir::Block>,
283 blocks: Vec<Option<mir::Block>>,
285 gpr: RegClass,
287 conv: &'static CallRegs,
290 stack: Stack,
292}
293
294impl<'a> Lowering<'a> {
295 fn new(source: &'a Func, names: &'a mut Interner, conv: &'static CallRegs) -> Self {
296 let counts = source.counts();
297 let name = source.name;
298 let mut uses = vec![0; counts.values];
299 for block in source.blocks() {
300 for inst in source.insts(block) {
301 for &arg in &source[source[inst].args] {
302 uses[arg.index()] += 1;
303 }
304 for call in source.successors(inst) {
305 for &arg in &source[call.args] {
306 uses[arg.index()] += 1;
307 }
308 }
309 }
310 }
311 Self {
312 source,
313 names,
314 out: mir::Func::new(name),
315 regs: vec![None; counts.values],
316 written: vec![None; counts.values],
317 blocks: vec![None; counts.blocks],
318 uses,
319 at: None,
320 gpr: x86_64::GPR,
321 conv,
322 stack: Stack::default(),
323 }
324 }
325
326 fn run(mut self) -> Result<Lowered, Unsupported> {
327 for block in self.source.blocks() {
331 let out = self.out.create_block();
332 self.blocks[block.index()] = Some(out);
333 }
334 for block in self.source.blocks() {
335 self.block(block)?;
336 }
337 Ok(Lowered { func: self.out, stack: self.stack })
338 }
339
340 fn block(&mut self, block: Block) -> Result<(), Unsupported> {
342 let out = self.out_block(block);
343 self.at = Some(out);
344 if self.source.entry() == Some(block) {
345 self.arrive(block, out)?;
346 } else {
347 for ¶m in self.source[block].params.iter() {
348 let reg = self.out.append_param(out, self.gpr);
349 self.regs[param.index()] = Some(reg);
350 }
351 }
352
353 let insts: Vec<Inst> = self.source.insts(block).collect();
359 let mut found: Vec<Option<Match<Term>>> = (0..insts.len()).map(|_| None).collect();
360 let mut folded: Vec<Inst> = Vec::new();
361 for (index, &inst) in insts.iter().enumerate().rev() {
362 if folded.contains(&inst) {
363 continue;
364 }
365 if let Some((plan, matched)) = self.select(inst) {
366 folded.extend(self.folds(inst, plan));
367 found[index] = Some(matched);
368 }
369 }
370
371 for (&inst, matched) in insts.iter().zip(found) {
372 if folded.contains(&inst) || self.writes_nothing(inst) {
373 continue;
374 }
375 match self.source[inst].opcode {
380 Opcode::Call => {
381 self.called(inst)?;
382 continue;
383 }
384 Opcode::CallIndirect => return Err(Unsupported::Indirect { inst }),
385 Opcode::Alloca => {
390 self.reserve(inst)?;
391 continue;
392 }
393 Opcode::GlobalAddr => {
400 self.address_of(inst)?;
401 continue;
402 }
403 Opcode::PtrToInt | Opcode::IntToPtr => {
407 self.rename(inst)?;
408 continue;
409 }
410 _ => {}
411 }
412 let matched = matched.ok_or_else(|| self.unsupported(inst))?;
413 self.emit(inst, &matched)?;
414 }
415 self.edges(block, out)
416 }
417
418 fn called(&mut self, inst: Inst) -> Result<(), Unsupported> {
424 let data = &self.source[inst];
425 let Extra::Call(info) = data.extra else { return Err(self.unsupported(inst)) };
426 let info = self.source[info];
427 let Some(callee) = info.callee else { return Err(Unsupported::Indirect { inst }) };
428
429 let values: Vec<Value> = self.source[data.args].to_vec();
430 let mut args = Vec::with_capacity(values.len());
431 for value in values {
432 args.push((self.source[value].ty, self.reg_of(value)?));
433 }
434 let signature = &self.source[info.signature];
435 let variadic = signature.variadic;
436 let returns = signature.return_types().next();
437 if signature.return_types().count() > 1 {
440 return Err(self.unsupported(inst));
441 }
442
443 let block = self.at.expect("a block is being filled");
444 let what = abi::Calling { callee, args: &args, returns, variadic };
445 let made = abi::call(&mut self.out, block, &what, self.conv, self.names)
446 .map_err(|refused| Unsupported::Call { inst, refused })?;
447 let calls = &mut self.stack.calls;
448 *calls = Some(calls.unwrap_or(0).max(made.outgoing));
449 if let (Some(result), Some(reg)) = (data.first_result, made.result) {
450 self.regs[result.index()] = Some(reg);
451 }
452 Ok(())
453 }
454
455 fn reserve(&mut self, inst: Inst) -> Result<(), Unsupported> {
469 let data = &self.source[inst];
470 if !self.source[data.args].is_empty() {
473 return Err(Unsupported::Dynamic { inst });
474 }
475 let Extra::Mem(mem) = data.extra else { return Err(self.unsupported(inst)) };
476 let info = self.source[mem];
477 let size = u32::try_from(info.size).map_err(|_| Unsupported::Dynamic { inst })?;
478 let result = data.first_result.ok_or_else(|| self.unsupported(inst))?;
479
480 let index = self.stack.locals.len();
484 self.stack.locals.push(Local { size, align: info.align.max(1) });
485
486 let block = self.at.expect("a block is being filled");
487 let reg = self.new_reg(result);
488 let span = self.source.span(inst);
489 let lea = mir::Opcode::new(self.names.intern(&format!("{PREFIX}{}", x86_64::FRAME.lea)));
490 let sp = mir::Operand::read(mir::Reg::physical(self.conv.stack_pointer), self.gpr);
491 let made =
492 self.out.build(block, lea).at(span).def(reg, self.gpr).mem(mir::Mem::at(sp)).finish();
493 self.stack.addresses.push((made, index));
494 Ok(())
495 }
496
497 fn address_of(&mut self, inst: Inst) -> Result<(), Unsupported> {
516 let data = &self.source[inst];
517 let Extra::Symbol(symbol) = data.extra else { return Err(self.unsupported(inst)) };
518 let result = data.first_result.ok_or_else(|| self.unsupported(inst))?;
519
520 let block = self.at.expect("a block is being filled");
521 let reg = self.new_reg(result);
522 let span = self.source.span(inst);
523 let lea = mir::Opcode::new(self.names.intern(&format!("{PREFIX}{}", x86_64::FRAME.lea)));
524 self.out.build(block, lea).at(span).def(reg, self.gpr).mem(mir::Mem::of(symbol)).finish();
525 Ok(())
526 }
527
528 fn rename(&mut self, inst: Inst) -> Result<(), Unsupported> {
542 let data = &self.source[inst];
543 let [arg] = self.source[data.args] else { return Err(self.unsupported(inst)) };
544 let result = data.first_result.ok_or_else(|| self.unsupported(inst))?;
545 if !self.is_address_width(self.source[arg].ty)
546 || !self.is_address_width(self.source[result].ty)
547 {
548 return Err(self.unsupported(inst));
549 }
550 let reg = self.reg_of(arg)?;
551 self.regs[result.index()] = Some(reg);
552 Ok(())
553 }
554
555 fn is_address_width(&self, ty: Type) -> bool {
557 ty.is_ptr() || (ty.is_int() && ty.bits() == ADDRESS_BITS)
558 }
559
560 fn edges(&mut self, block: Block, out: mir::Block) -> Result<(), Unsupported> {
574 let Some(term) = self.source.terminator(block) else { return Ok(()) };
575 let branch =
576 if self.source[term].opcode == Opcode::BrIf { self.out.terminator(out) } else { None };
577
578 let calls: Vec<rucc_ir::BlockCall> = self.source.successors(term).collect();
579 let mut succs = Vec::with_capacity(calls.len());
580 for call in calls {
581 let args: Vec<Value> = self.source[call.args].to_vec();
582 let mut regs = Vec::with_capacity(args.len());
583 for value in args {
584 regs.push(self.reg_of(value)?);
585 }
586 succs.push(mir::BlockCall { block: self.out_block(call.block), args: regs });
587 }
588 if let Some(branch) = branch {
589 if self.out.terminator(out) != Some(branch) {
590 self.out.remove_inst(branch);
591 self.out.append_inst(out, branch);
592 }
593 }
594 *self.out.succs_mut(out) = succs;
595 Ok(())
596 }
597
598 fn out_block(&self, block: Block) -> mir::Block {
600 self.blocks[block.index()].expect("every block was created before any was filled")
601 }
602
603 fn arrive(&mut self, block: Block, out: mir::Block) -> Result<(), Unsupported> {
610 let params = self.source[block].params.clone();
611 let types: Vec<Type> = params.iter().map(|&value| self.source[value].ty).collect();
612 let regs = abi::entry(&mut self.out, out, &types, self.conv, self.names)
613 .map_err(|(index, missing)| Unsupported::Argument { index, missing })?;
614 for (¶m, reg) in params.iter().zip(regs) {
615 self.regs[param.index()] = Some(reg);
616 }
617 Ok(())
618 }
619
620 fn writes_nothing(&self, inst: Inst) -> bool {
632 let data = &self.source[inst];
633 match data.opcode {
634 Opcode::IConst | Opcode::Jump => true,
635 Opcode::Return => self.source[data.args].is_empty(),
636 _ => false,
637 }
638 }
639
640 fn select(&self, inst: Inst) -> Option<(Plan, Match<Term>)> {
646 for plan in self.plans(inst) {
647 let terms = Terms::new(self.source, inst, plan);
648 if let Some(matched) = TABLE.find(&terms, Term::Root) {
649 return Some((plan, matched));
650 }
651 }
652 None
653 }
654
655 fn plans(&self, inst: Inst) -> Vec<Plan> {
657 let args = &self.source[self.source[inst].args];
658 let mut plans = vec![PLAIN];
659 for (index, &arg) in args.iter().enumerate().take(MAX_ARGS) {
660 let mut ways = Vec::new();
661 if self.foldable(inst, arg) {
662 ways.push(Shown::Expand);
663 }
664 if Terms::new(self.source, inst, PLAIN).constant(arg).is_some() {
665 ways.push(Shown::Const);
666 }
667 ways.push(Shown::Reg);
668 plans = plans
669 .into_iter()
670 .flat_map(|plan| {
671 ways.iter().map(move |&way| {
672 let mut next = plan;
673 next[index] = way;
674 next
675 })
676 })
677 .collect();
678 }
679 plans
680 }
681
682 fn foldable(&self, into: Inst, value: Value) -> bool {
690 let Def::Result { inst, .. } = self.source[value].def else { return false };
691 if self.source[inst].opcode == Opcode::IConst || self.uses[value.index()] != 1 {
692 return false;
693 }
694 self.source.block_of(inst).is_some()
695 && self.source.block_of(inst) == self.source.block_of(into)
696 }
697
698 fn folds(&self, inst: Inst, plan: Plan) -> Vec<Inst> {
705 let args = &self.source[self.source[inst].args];
706 args.iter()
707 .take(MAX_ARGS)
708 .enumerate()
709 .filter(|&(index, _)| plan[index] == Shown::Expand)
710 .filter_map(|(_, &arg)| match self.source[arg].def {
711 Def::Result { inst, .. } => Some(inst),
712 Def::Param { .. } => None,
713 })
714 .collect()
715 }
716
717 fn emit(&mut self, inst: Inst, matched: &Match<Term>) -> Result<(), Unsupported> {
719 let rule: &Rule = TABLE.rule(matched);
720 let pieces = rule.replacement;
721 let Some(Piece::App { head, arity }) = pieces.first() else {
722 return Err(self.unsupported(inst));
723 };
724 let opcode = head.strip_prefix(PREFIX).ok_or_else(|| self.unsupported(inst))?;
725 let form = x86_64::form(opcode).ok_or_else(|| self.unsupported(inst))?;
726
727 let mut read = Read::default();
728 let mut at = 1;
729 for _ in 0..*arity {
730 at = self.read(inst, pieces, at, &matched.bindings, &mut read)?;
731 }
732
733 let descs = form.operands();
734 let writes = descs.iter().take_while(|desc| desc.role.is_def()).count();
735 if descs.len() - writes != read.regs.len() {
736 return Err(self.unsupported(inst));
737 }
738
739 let mut regs = Vec::new();
745 if writes > 0 {
746 let result = self.source[inst].first_result.ok_or_else(|| self.unsupported(inst))?;
747 regs.push(self.new_reg(result));
748 regs.extend((1..writes).map(|_| self.out.new_vreg(self.gpr)));
749 } else if self.source[inst].first_result.is_some() {
750 return Err(self.unsupported(inst));
753 }
754 regs.extend(read.regs.iter().copied());
755
756 let block = self.at.expect("a block is being filled");
757 let opcode = mir::Opcode::new(self.names.intern(head));
758 let mut build = self.out.build(block, opcode).at(self.source.span(inst));
759 for (desc, reg) in descs.iter().zip(regs) {
760 let operand = mir::Operand {
761 reg,
762 class: desc.class,
763 role: desc.role,
764 constraint: desc.constraint,
765 };
766 build = build.operand(operand);
767 }
768 if let Some(mem) = read.mem {
769 build = build.mem(mem);
770 }
771 if let Some(imm) = read.imm {
772 build = build.imm(imm);
773 }
774 build.finish();
775 Ok(())
776 }
777
778 fn read(
783 &mut self,
784 inst: Inst,
785 pieces: &'static [Piece],
786 at: usize,
787 bindings: &[Term],
788 out: &mut Read,
789 ) -> Result<usize, Unsupported> {
790 match pieces.get(at) {
791 Some(Piece::Int(value)) => {
792 out.imm = i64::try_from(*value).ok();
793 Ok(at + 1)
794 }
795 Some(Piece::Var { index, .. }) => {
796 match bindings.get(*index) {
797 Some(&Term::Reg(value)) => {
798 let reg = self.reg_of(value)?;
799 out.regs.push(reg);
800 }
801 Some(&Term::Num(value)) => out.imm = i64::try_from(value).ok(),
802 _ => return Err(self.unsupported(inst)),
805 }
806 Ok(at + 1)
807 }
808 Some(Piece::App { head, arity }) => {
809 let kind = x86_64::address(head).ok_or_else(|| self.unsupported(inst))?;
810 let mut inner = Read::default();
811 let mut next = at + 1;
812 for _ in 0..*arity {
813 next = self.read(inst, pieces, next, bindings, &mut inner)?;
814 }
815 let mem = address(kind, &inner, self.gpr).ok_or_else(|| self.unsupported(inst))?;
816 out.mem = Some(mem);
817 Ok(next)
818 }
819 None => Err(self.unsupported(inst)),
820 }
821 }
822
823 fn reg_of(&mut self, value: Value) -> Result<mir::Reg, Unsupported> {
836 let constant = match self.source[value].def {
837 Def::Result { inst, .. } => {
838 (self.source[inst].opcode == Opcode::IConst).then_some(inst)
839 }
840 Def::Param { .. } => None,
841 };
842 let here = self.at.expect("a block is being filled");
843 if let Some(reg) = self.regs[value.index()] {
844 if constant.is_none() || self.written[value.index()] == Some(here) {
845 return Ok(reg);
846 }
847 }
848 if let Some(inst) = constant {
849 self.regs[value.index()] = None;
852 let matched = self
853 .select(inst)
854 .map(|(_, matched)| matched)
855 .ok_or_else(|| self.unsupported(inst))?;
856 self.emit(inst, &matched)?;
857 self.written[value.index()] = Some(here);
858 return Ok(self.regs[value.index()].expect("a constant is written into a register"));
859 }
860 Ok(self.new_reg(value))
861 }
862
863 fn new_reg(&mut self, value: Value) -> mir::Reg {
865 if let Some(reg) = self.regs[value.index()] {
866 return reg;
867 }
868 let reg = self.out.new_vreg(self.gpr);
869 self.regs[value.index()] = Some(reg);
870 reg
871 }
872
873 fn unsupported(&self, inst: Inst) -> Unsupported {
874 let data = &self.source[inst];
875 Unsupported::Inst {
876 inst,
877 term: Terms::new(self.source, inst, PLAIN).name(inst),
878 opcode: data.opcode,
879 ty: data.first_result.map(|result| self.source[result].ty),
880 }
881 }
882}
883
884#[derive(Debug, Default)]
886struct Read {
887 regs: Vec<mir::Reg>,
888 imm: Option<i64>,
889 mem: Option<mir::Mem>,
890}
891
892fn address(kind: x86_64::Address, read: &Read, gpr: RegClass) -> Option<mir::Mem> {
898 let mut regs = read.regs.iter().copied().map(|reg| mir::Operand::read(reg, gpr));
899 match kind {
900 x86_64::Address::BaseIndexScale => {
901 let base = regs.next()?;
902 let index = regs.next()?;
903 Some(mir::Mem::at(base).indexed(index, u8::try_from(read.imm?).ok()?))
904 }
905 x86_64::Address::IndexScale => Some(mir::Mem {
906 base: None,
907 index: Some(regs.next()?),
908 scale: u8::try_from(read.imm?).ok()?,
909 disp: 0,
910 symbol: None,
911 }),
912 x86_64::Address::Base => Some(mir::Mem::at(regs.next()?)),
913 x86_64::Address::BaseOffset => {
916 Some(mir::Mem { disp: i32::try_from(read.imm?).ok()?, ..mir::Mem::at(regs.next()?) })
917 }
918 }
919}
920
921static TABLE: &Table = &crate::select::x86_64::TABLE;
927
928#[cfg(test)]
929mod tests {
930 use rucc_ir::{Builder, CallInfo, Flags, InstData, MemInfo, MemOrder, Signature, Type};
931 use rucc_regalloc::assign::Env;
932 use rucc_target::x86_64::{FRAME, REGS, SYSV};
933
934 use super::*;
935 use crate::finish::finish;
936 use crate::frame::{Frame, Layout};
937
938 fn blank(params: &[Type]) -> (Interner, Func, Block, Vec<Value>) {
940 let mut names = Interner::new();
941 let mut func = Func::new(names.intern("f"), Signature::new());
942 let block = func.create_block();
943 let values = params.iter().map(|&ty| func.append_param(block, ty)).collect();
944 (names, func, block, values)
945 }
946
947 fn plain() -> MemInfo {
950 MemInfo { size: 0, align: 1, order: MemOrder::NotAtomic, tbaa: None }
951 }
952
953 fn env() -> Env {
958 const SCRATCH: [rucc_target::PhysReg; 2] = [x86_64::R10, x86_64::R11];
959 let order: Vec<rucc_target::PhysReg> =
960 SYSV.int_order.iter().copied().filter(|reg| !SCRATCH.contains(reg)).collect();
961 Env::new().with(x86_64::GPR, &order, &SCRATCH)
962 }
963
964 fn lower(names: &mut Interner, source: &Func) -> String {
966 let out = func(source, names, &SYSV).expect("every instruction has a rule");
967 mir::print_func(&out.func, names, ®S)
968 }
969
970 #[test]
971 fn an_addition_of_two_registers_is_one_instruction() {
972 let i32 = Type::int(32);
973 let (mut names, mut func, block, args) = blank(&[i32, i32]);
974 let mut build = Builder::new(&mut func, block);
975 build.binary(Opcode::Add, args[0], args[1], Flags::default());
976
977 assert_eq!(
978 lower(&mut names, &func),
979 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
980 %1:gpr($rsi) = x64.arg_val_32\n %2:gpr(reuse 1) = x64.add_rr_32 %0, %1\n}\n"
981 );
982 }
983
984 #[test]
985 fn a_constant_operand_becomes_an_immediate() {
986 let i32 = Type::int(32);
987 let (mut names, mut func, block, args) = blank(&[i32]);
988 let mut build = Builder::new(&mut func, block);
989 let seven = build.iconst(i32, 7);
990 build.binary(Opcode::Add, args[0], seven, Flags::default());
991
992 assert_eq!(
995 lower(&mut names, &func),
996 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
997 %1:gpr(reuse 1) = x64.add_ri_32 %0, 7\n}\n"
998 );
999 }
1000
1001 #[test]
1002 fn a_constant_too_wide_for_an_immediate_goes_into_a_register() {
1003 let i64 = Type::int(64);
1004 let (mut names, mut func, block, args) = blank(&[i64]);
1005 let mut build = Builder::new(&mut func, block);
1006 let big = build.iconst(i64, i128::from(i32::MAX) + 1);
1007 build.binary(Opcode::Add, args[0], big, Flags::default());
1008
1009 assert_eq!(
1013 lower(&mut names, &func),
1014 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
1015 %1:gpr = x64.mov_ri_64 2147483648\n %2:gpr(reuse 1) = x64.add_rr_64 %0, %1\n}\n"
1016 );
1017 }
1018
1019 #[test]
1020 fn an_index_calculation_folds_into_an_address() {
1021 let i64 = Type::int(64);
1022 let (mut names, mut func, block, args) = blank(&[i64, i64]);
1023 let mut build = Builder::new(&mut func, block);
1024 let four = build.iconst(i64, 4);
1025 let scaled = build.binary(Opcode::Mul, args[1], four, Flags::default());
1026 build.binary(Opcode::Add, args[0], scaled, Flags::default());
1027
1028 assert_eq!(
1031 lower(&mut names, &func),
1032 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
1033 %1:gpr($rsi) = x64.arg_val_64\n %2:gpr = x64.lea_64 [%0 + %1*4]\n}\n"
1034 );
1035 }
1036
1037 #[test]
1038 fn an_instruction_read_twice_is_not_folded_into_either_reader() {
1039 let i64 = Type::int(64);
1040 let (mut names, mut func, block, args) = blank(&[i64, i64]);
1041 let mut build = Builder::new(&mut func, block);
1042 let four = build.iconst(i64, 4);
1043 let scaled = build.binary(Opcode::Mul, args[1], four, Flags::default());
1044 let first = build.binary(Opcode::Add, args[0], scaled, Flags::default());
1045 build.binary(Opcode::Add, first, scaled, Flags::default());
1046
1047 let text = lower(&mut names, &func);
1050 assert!(text.contains("x64.lea_64 [%1*4]"), "{text}");
1051 assert_eq!(text.matches("x64.add_rr_64").count(), 2, "{text}");
1052 }
1053
1054 #[test]
1055 fn a_shift_by_a_register_asks_for_it_in_cl() {
1056 let i32 = Type::int(32);
1057 let (mut names, mut func, block, args) = blank(&[i32, i32]);
1058 let mut build = Builder::new(&mut func, block);
1059 build.binary(Opcode::Shl, args[0], args[1], Flags::default());
1060
1061 let text = lower(&mut names, &func);
1064 assert!(text.contains("x64.shl_rcl_32 %0, %1($rcx)"), "{text}");
1065 }
1066
1067 #[test]
1068 fn a_division_names_the_registers_and_the_register_it_destroys() {
1069 let i32 = Type::int(32);
1070 let (mut names, mut func, block, args) = blank(&[i32, i32]);
1071 let mut build = Builder::new(&mut func, block);
1072 build.binary(Opcode::SDiv, args[0], args[1], Flags::default());
1073
1074 let text = lower(&mut names, &func);
1077 assert!(
1078 text.contains("%2:gpr($rax), early %3:gpr($rdx) = x64.idiv_quo_32 %0($rax), %1"),
1079 "{text}"
1080 );
1081 }
1082
1083 #[test]
1084 fn a_load_reads_through_the_register_the_address_is_in() {
1085 let i64 = Type::int(64);
1086 let (mut names, mut func, block, args) = blank(&[i64]);
1087 let mut build = Builder::new(&mut func, block);
1088 build.load(Type::int(32), args[0], plain(), Flags::default());
1089
1090 assert_eq!(
1091 lower(&mut names, &func),
1092 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
1093 %1:gpr = x64.mov_rm_32 [%0]\n}\n"
1094 );
1095 }
1096
1097 #[test]
1098 fn a_store_writes_no_register_and_the_value_it_writes_is_the_one_the_ir_gave_it() {
1099 let (mut names, mut func, block, args) = blank(&[Type::int(32), Type::int(64)]);
1100 let mut build = Builder::new(&mut func, block);
1101 build.store(args[0], args[1], plain(), Flags::default());
1102
1103 assert_eq!(
1107 lower(&mut names, &func),
1108 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
1109 %1:gpr($rsi) = x64.arg_val_64\n x64.mov_mr_32 %0, [%1]\n}\n"
1110 );
1111 }
1112
1113 #[test]
1114 fn an_address_with_a_constant_added_folds_into_the_access() {
1115 let i64 = Type::int(64);
1116 let (mut names, mut func, block, args) = blank(&[i64]);
1117 let mut build = Builder::new(&mut func, block);
1118 let twelve = build.iconst(i64, 12);
1119 let field = build.binary(Opcode::Add, args[0], twelve, Flags::default());
1120 build.load(Type::int(64), field, plain(), Flags::default());
1121
1122 assert_eq!(
1125 lower(&mut names, &func),
1126 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
1127 %1:gpr = x64.mov_rm_64 [%0 + 12]\n}\n"
1128 );
1129 }
1130
1131 #[test]
1132 fn a_displacement_too_wide_to_encode_leaves_the_addition_where_it_is() {
1133 let i64 = Type::int(64);
1134 let (mut names, mut func, block, args) = blank(&[i64]);
1135 let mut build = Builder::new(&mut func, block);
1136 let big = build.iconst(i64, i128::from(i32::MAX) + 1);
1137 let far = build.binary(Opcode::Add, args[0], big, Flags::default());
1138 build.load(Type::int(32), far, plain(), Flags::default());
1139
1140 let text = lower(&mut names, &func);
1144 assert!(text.contains("x64.mov_rm_32 [%2]"), "{text}");
1145 assert!(text.contains("x64.add_rr_64"), "{text}");
1146 }
1147
1148 #[test]
1149 fn a_store_of_a_value_that_was_loaded_is_two_instructions_and_no_arithmetic() {
1150 let i64 = Type::int(64);
1151 let (mut names, mut func, block, args) = blank(&[i64, i64]);
1152 let mut build = Builder::new(&mut func, block);
1153 let got = build.load(Type::int(8), args[0], plain(), Flags::default());
1154 build.store(got, args[1], plain(), Flags::default());
1155
1156 assert_eq!(
1160 lower(&mut names, &func),
1161 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
1162 %1:gpr($rsi) = x64.arg_val_64\n %2:gpr = x64.mov_rm_8 [%0]\n \
1163 x64.mov_mr_8 %2, [%1]\n}\n"
1164 );
1165 }
1166
1167 #[test]
1168 fn an_access_at_a_width_no_rule_is_written_at_is_reported() {
1169 let i64 = Type::int(64);
1170 let (mut names, mut source, block, args) = blank(&[i64]);
1171 let mut build = Builder::new(&mut source, block);
1172 build.load(Type::int(128), args[0], plain(), Flags::default());
1173
1174 let failed = func(&source, &mut names, &SYSV).expect_err("nothing loads 128 bits");
1178 assert_eq!(failed.to_string(), "no rule lowers a `load` producing a `i128`");
1179 }
1180
1181 #[test]
1182 fn a_return_asks_for_the_value_in_the_register_the_caller_reads() {
1183 let (mut names, mut func, block, args) = blank(&[Type::int(32)]);
1184 let mut build = Builder::new(&mut func, block);
1185 build.ret(&[args[0]]);
1186
1187 assert_eq!(
1192 lower(&mut names, &func),
1193 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
1194 x64.ret_val_32 %0($rax)\n}\n"
1195 );
1196 }
1197
1198 #[test]
1199 fn a_return_of_a_constant_puts_it_in_a_register_first() {
1200 let (mut names, mut func, block, _) = blank(&[]);
1201 let mut build = Builder::new(&mut func, block);
1202 let zero = build.iconst(Type::int(32), 0);
1203 build.ret(&[zero]);
1204
1205 assert_eq!(
1209 lower(&mut names, &func),
1210 "mfunc @f {\nblock0:\n %0:gpr = x64.mov_ri_32 0\n x64.ret_val_32 %0($rax)\n}\n"
1211 );
1212 }
1213
1214 #[test]
1215 fn a_return_of_nothing_is_no_instruction_at_all() {
1216 let (mut names, mut func, block, _) = blank(&[]);
1217 let mut build = Builder::new(&mut func, block);
1218 build.ret(&[]);
1219
1220 assert_eq!(lower(&mut names, &func), "mfunc @f {\nblock0:\n}\n");
1224 }
1225
1226 #[test]
1227 fn the_allocator_is_what_moves_the_answer_into_the_return_register() {
1228 let (mut names, mut source, block, _) = blank(&[]);
1229 let mut build = Builder::new(&mut source, block);
1230 let zero = build.iconst(Type::int(32), 0);
1231 build.ret(&[zero]);
1232
1233 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1234 let env = env();
1235 let allocation = rucc_regalloc::run(&mut out, &env);
1236 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
1237 finish(&mut out, &allocation, &frame, &[], &SYSV, &FRAME, &mut names);
1238
1239 assert_eq!(
1250 mir::print_func(&out, &names, ®S),
1251 "mfunc @f {\nblock0:\n $rcx = x64.mov_ri_32 0\n $rax = x64.mov_rr_64 $rcx\n \
1252 x64.ret_val_32 $rax($rax)\n x64.ret\n}\n"
1253 );
1254 }
1255
1256 #[test]
1257 fn a_function_of_two_arguments_is_a_whole_function_now() {
1258 let i32 = Type::int(32);
1259 let (mut names, mut source, block, args) = blank(&[i32, i32]);
1260 let mut build = Builder::new(&mut source, block);
1261 let sum = build.binary(Opcode::Add, args[0], args[1], Flags::default());
1262 build.ret(&[sum]);
1263
1264 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1265 let env = env();
1266 let allocation = rucc_regalloc::run(&mut out, &env);
1267 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
1268 finish(&mut out, &allocation, &frame, &[], &SYSV, &FRAME, &mut names);
1269
1270 assert_eq!(
1286 mir::print_func(&out, &names, ®S),
1287 "mfunc @f {\nblock0:\n $rdi($rdi) = x64.arg_val_32\n \
1288 $rax = x64.mov_rr_64 $rdi\n $rsi($rsi) = x64.arg_val_32\n \
1289 $rcx = x64.mov_rr_64 $rsi\n $rdx = x64.mov_rr_64 $rax\n \
1290 $rdx(reuse 1) = x64.add_rr_32 $rax, $rcx\n $rax = x64.mov_rr_64 $rdx\n \
1291 x64.ret_val_32 $rax($rax)\n x64.ret\n}\n"
1292 );
1293 }
1294
1295 #[test]
1296 fn an_argument_with_no_register_left_for_it_is_reported() {
1297 let i64 = Type::int(64);
1298 let (mut names, mut source, block, args) = blank(&[i64; 7]);
1299 let mut build = Builder::new(&mut source, block);
1300 build.ret(&[args[6]]);
1301
1302 let failed = func(&source, &mut names, &SYSV).expect_err("the seventh is on the stack");
1306 assert_eq!(failed.to_string(), "parameter 6 is passed on the stack");
1307 }
1308
1309 #[test]
1310 fn a_jump_is_the_edge_and_nothing_else() {
1311 let i32 = Type::int(32);
1312 let (mut names, mut source, entry, args) = blank(&[i32]);
1313 let next = source.create_block();
1314 let got = source.append_param(next, i32);
1315 Builder::new(&mut source, entry).jump(next, &[args[0]]);
1316 Builder::new(&mut source, next).ret(&[got]);
1317
1318 assert_eq!(
1321 lower(&mut names, &source),
1322 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32 block1(%0)\n\n\
1323 block1(%1:gpr):\n x64.ret_val_32 %1($rax)\n}\n"
1324 );
1325 }
1326
1327 #[test]
1333 fn a_constant_two_blocks_want_is_written_in_both_of_them() {
1334 let i32 = Type::int(32);
1335 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1336 let then = source.create_block();
1337 let other = source.create_block();
1338 let join = source.create_block();
1339 let got = source.append_param(join, i32);
1340
1341 let mut build = Builder::new(&mut source, entry);
1342 let seven = build.iconst(i32, 7);
1343 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1344 build.br_if(cond, then, &[], other, &[]);
1345 Builder::new(&mut source, then).jump(join, &[seven]);
1348 Builder::new(&mut source, other).jump(join, &[seven]);
1349 Builder::new(&mut source, join).ret(&[got]);
1350
1351 let text = lower(&mut names, &source);
1352 assert_eq!(text.matches("x64.mov_ri_32 7").count(), 2, "one seven per block: {text}");
1353 }
1354
1355 #[test]
1359 fn a_constant_an_edge_wants_is_written_before_the_branch_and_not_after_it() {
1360 let i32 = Type::int(32);
1361 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1362 let then = source.create_block();
1363 let join = source.create_block();
1364 let got = source.append_param(join, i32);
1365
1366 let mut build = Builder::new(&mut source, entry);
1367 let nine = build.iconst(i32, 9);
1368 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1369 build.br_if(cond, then, &[], join, &[nine]);
1370 Builder::new(&mut source, then).jump(join, &[args[0]]);
1371 Builder::new(&mut source, join).ret(&[got]);
1372
1373 let out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1374 let entry = out.entry().expect("an entry block");
1375 let last = out.terminator(entry).expect("a block that leaves two ways has a branch");
1376 let branch = names.intern("x64.br_cond_8");
1377 assert_eq!(
1378 out[last].opcode,
1379 mir::Opcode::new(branch),
1380 "the branch is last: {}",
1381 mir::print_func(&out, &names, ®S)
1382 );
1383 }
1384
1385 #[test]
1386 fn a_conditional_branch_is_lowered_to_the_condition_and_nothing_about_where_it_goes() {
1387 let i32 = Type::int(32);
1388 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1389 let then = source.create_block();
1390 let other = source.create_block();
1391 let mut build = Builder::new(&mut source, entry);
1392 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1393 build.br_if(cond, then, &[], other, &[]);
1394 Builder::new(&mut source, then).ret(&[args[0]]);
1395 Builder::new(&mut source, other).ret(&[args[1]]);
1396
1397 assert_eq!(
1401 lower(&mut names, &source),
1402 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
1403 %1:gpr($rsi) = x64.arg_val_32\n %2:gpr = x64.cmp_set_l_32 %0, %1\n \
1404 x64.br_cond_8 %2, block1, block2\n\n\
1405 block1:\n x64.ret_val_32 %0($rax)\n\n\
1406 block2:\n x64.ret_val_32 %1($rax)\n}\n"
1407 );
1408 }
1409
1410 #[test]
1411 fn a_branch_over_a_block_is_a_whole_function_now() {
1412 let i32 = Type::int(32);
1413 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1414 let then = source.create_block();
1415 let other = source.create_block();
1416 let join = source.create_block();
1417 let got = source.append_param(join, i32);
1418 let mut build = Builder::new(&mut source, entry);
1419 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1420 build.br_if(cond, then, &[], other, &[]);
1421 let mut build = Builder::new(&mut source, then);
1422 let sum = build.binary(Opcode::Add, args[0], args[1], Flags::default());
1423 build.jump(join, &[sum]);
1424 Builder::new(&mut source, other).jump(join, &[args[1]]);
1425 Builder::new(&mut source, join).ret(&[got]);
1426
1427 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1433 assert_eq!(crate::split::critical(&mut out), 0, "no edge here is critical");
1434 let env = env();
1435 let allocation = rucc_regalloc::run(&mut out, &env);
1436 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
1437 finish(&mut out, &allocation, &frame, &[], &SYSV, &FRAME, &mut names);
1438
1439 let text = mir::print_func(&out, &names, ®S);
1444 assert_eq!(text.matches("x64.ret\n").count(), 1, "{text}");
1445 assert!(text.contains("x64.br_cond_8"), "{text}");
1446 assert!(text.contains("x64.add_rr_32"), "{text}");
1447 assert!(!text.contains('%'), "{text}");
1448 }
1449
1450 #[test]
1451 fn a_critical_edge_is_split_before_the_allocator_ever_sees_it() {
1452 let i32 = Type::int(32);
1453 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1454 let then = source.create_block();
1455 let join = source.create_block();
1456 let got = source.append_param(join, i32);
1457 let mut build = Builder::new(&mut source, entry);
1458 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1459 build.br_if(cond, then, &[], join, &[args[1]]);
1460 Builder::new(&mut source, then).jump(join, &[args[0]]);
1461 let mut build = Builder::new(&mut source, join);
1462 let twice = build.binary(Opcode::Add, got, got, Flags::default());
1463 build.ret(&[twice]);
1464
1465 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1470 assert_eq!(crate::split::critical(&mut out), 1);
1471 let env = env();
1472 let allocation = rucc_regalloc::run(&mut out, &env);
1473 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
1474 finish(&mut out, &allocation, &frame, &[], &SYSV, &FRAME, &mut names);
1475
1476 let text = mir::print_func(&out, &names, ®S);
1478 assert_eq!(out.block_count(), 4, "{text}");
1479 assert_eq!(text.matches("x64.ret\n").count(), 1, "{text}");
1480 }
1481
1482 #[test]
1483 fn a_call_passes_what_the_convention_says_and_takes_back_what_it_says() {
1484 let i32 = Type::int(32);
1485 let (mut names, mut source, block, args) = blank(&[i32, i32]);
1486 let sig =
1487 source.add_signature(Signature::new().with_params(&[i32, i32]).with_returns(&[i32]));
1488 let callee = names.intern("g");
1489 let call = Builder::new(&mut source, block).call(callee, sig, &[args[0], args[1]]);
1490 let got = source[call].first_result.expect("an integer comes back");
1491 Builder::new(&mut source, block).ret(&[got]);
1492
1493 let text = lower(&mut names, &source);
1497 assert!(text.contains("= x64.call %0($rdi), %1($rsi), @g"), "{text}");
1498 assert!(text.contains("x64.ret_val_32 %2($rax)"), "{text}");
1499 assert!(text.contains("%2:gpr($rax), $rcx, $rdx, $r8, $r9, $r10, $r11, $xmm0,"), "{text}");
1503 assert!(text.contains("$xmm15 = x64.call"), "{text}");
1504 }
1505
1506 #[test]
1507 fn what_the_frame_owes_a_call_comes_back_with_the_function() {
1508 let i32 = Type::int(32);
1509 let sig = |source: &mut Func| source.add_signature(Signature::new().with_params(&[i32]));
1510
1511 let (mut names, mut source, block, args) = blank(&[i32]);
1512 let sig = sig(&mut source);
1513 let callee = names.intern("g");
1514 Builder::new(&mut source, block).call(callee, sig, &[args[0]]);
1515 let out = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1516
1517 assert_eq!(out.stack.calls, Some(0));
1520 let layout = out.stack.layout(Layout::new(&SYSV, REGS));
1521 assert!(!layout.leaf);
1522 assert_eq!(layout.outgoing, 0);
1523
1524 let out = func(&source, &mut names, &x86_64::WIN64).expect("every instruction has a rule");
1527 assert_eq!(out.stack.calls, Some(32));
1528
1529 let (mut names, mut source, block, args) = blank(&[i32]);
1531 Builder::new(&mut source, block).ret(&[args[0]]);
1532 let out = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1533 assert_eq!(out.stack.calls, None);
1534 assert!(out.stack.layout(Layout::new(&SYSV, REGS)).leaf);
1535 }
1536
1537 #[test]
1538 fn a_value_that_outlives_a_call_is_not_left_where_the_call_destroys_it() {
1539 let i32 = Type::int(32);
1540 let (mut names, mut source, block, args) = blank(&[i32]);
1541 let sig = source.add_signature(Signature::new().with_params(&[i32]).with_returns(&[i32]));
1542 let callee = names.intern("g");
1543 let call = Builder::new(&mut source, block).call(callee, sig, &[args[0]]);
1544 let got = source[call].first_result.expect("an integer comes back");
1545 let mut build = Builder::new(&mut source, block);
1546 let sum = build.binary(Opcode::Add, got, args[0], Flags::default());
1547 build.ret(&[sum]);
1548
1549 let lowered = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1552 let layout = lowered.stack.layout(Layout::new(&SYSV, REGS));
1553 let mut out = lowered.func;
1554 let env = env();
1555 let allocation = rucc_regalloc::run(&mut out, &env);
1556 let frame = Frame::of(&out, &allocation, &layout);
1557 finish(&mut out, &allocation, &frame, &[], &SYSV, &FRAME, &mut names);
1558
1559 let text = mir::print_func(&out, &names, ®S);
1562 assert!(text.contains("$rbx"), "{text}");
1563 assert!(!text.contains('%'), "{text}");
1564 assert_eq!(text.matches("x64.call").count(), 1, "{text}");
1565 }
1566
1567 #[test]
1568 fn a_call_this_cannot_make_is_reported_rather_than_made() {
1569 let i64 = Type::int(64);
1570 let (mut names, mut source, block, args) = blank(&[i64]);
1571 let seven = vec![i64; 7];
1572 let sig = source.add_signature(Signature::new().with_params(&seven));
1573 let callee = names.intern("g");
1574 let passed = vec![args[0]; 7];
1575 Builder::new(&mut source, block).call(callee, sig, &passed);
1576
1577 let failed = func(&source, &mut names, &SYSV).expect_err("the seventh is on the stack");
1580 assert_eq!(failed.to_string(), "argument 6 of this call is passed on the stack");
1581
1582 let (mut names, mut source, block, _) = blank(&[]);
1583 let sig = source
1584 .add_signature(Signature::new().with_returns(&[Type::float(rucc_ir::Float::F64)]));
1585 let callee = names.intern("g");
1586 Builder::new(&mut source, block).call(callee, sig, &[]);
1587 let failed = func(&source, &mut names, &SYSV).expect_err("a double comes back in xmm0");
1588 assert_eq!(failed.to_string(), "what this call gives back is in a vector register");
1589 }
1590
1591 #[test]
1592 fn a_call_through_an_address_is_reported_as_one() {
1593 let i32 = Type::int(32);
1594 let (mut names, mut source, block, args) = blank(&[i32]);
1595 let sig = source.add_signature(Signature::new().with_params(&[i32]));
1596 let varargs = source.push_abis(&[]);
1597 let info = source.add_call(CallInfo { callee: None, signature: sig, varargs });
1598 let mut build = Builder::new(&mut source, block);
1599 let inst = InstData {
1600 args: build.func().push_values(&[args[0], args[0]]),
1601 extra: Extra::Call(info),
1602 ..InstData::new(Opcode::CallIndirect)
1603 };
1604 let called = build.inst(inst, &[]);
1605
1606 let failed = func(&source, &mut names, &SYSV).expect_err("nothing calls through a value");
1609 assert_eq!(failed.to_string(), "no rule calls through an address");
1610 assert_eq!(failed.inst(), Some(called), "the call is what a message about it points at");
1611 }
1612
1613 #[test]
1614 fn an_instruction_no_rule_covers_is_reported() {
1615 let i64 = Type::int(64);
1616 let (mut names, mut source, block, args) = blank(&[i64, i64]);
1617 let mut build = Builder::new(&mut source, block);
1618 build.ret(&[args[0], args[1]]);
1619
1620 let failed = func(&source, &mut names, &SYSV).expect_err("nothing returns two values");
1623 assert_eq!(failed.to_string(), "no rule lowers a `return`");
1624
1625 let inst = failed.inst().expect("the instruction it is about");
1628 assert_eq!(source[inst].opcode, Opcode::Return);
1629 }
1630
1631 #[test]
1638 fn a_refusal_about_a_parameter_has_no_instruction_to_point_at() {
1639 let missing = Unsupported::Argument { index: 0, missing: Missing::OnStack };
1640 assert_eq!(missing.inst(), None);
1641 }
1642
1643 fn slot(source: &mut Func, block: Block, size: u64, align: u32) -> Value {
1645 let info = MemInfo { size, align, ..plain() };
1646 let mut build = Builder::new(source, block);
1647 let mem = build.func().add_mem(info);
1648 build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
1649 }
1650
1651 #[test]
1652 fn a_local_is_memory_in_the_frame_and_one_instruction_that_says_where() {
1653 let (mut names, mut source, block, _) = blank(&[]);
1654 let slot = slot(&mut source, block, 4, 4);
1655 let mut build = Builder::new(&mut source, block);
1656 let nine = build.iconst(Type::int(32), 9);
1657 build.store(nine, slot, plain(), Flags::default());
1658 let loaded = build.load(Type::int(32), slot, plain(), Flags::default());
1659 build.ret(&[loaded]);
1660
1661 let lowered = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1662
1663 assert_eq!(lowered.stack.locals, vec![Local { size: 4, align: 4 }]);
1667 assert_eq!(lowered.stack.addresses.len(), 1);
1668 assert_eq!(lowered.stack.addresses[0].1, 0);
1669 assert_eq!(
1670 mir::print_func(&lowered.func, &names, ®S),
1671 "mfunc @f {\nblock0:\n %0:gpr = x64.lea_64 [$rsp]\n \
1672 %1:gpr = x64.mov_ri_32 9\n x64.mov_mr_32 %1, [%0]\n \
1673 %2:gpr = x64.mov_rm_32 [%0]\n x64.ret_val_32 %2($rax)\n}\n"
1674 );
1675 }
1676
1677 #[test]
1678 fn the_frame_is_what_fills_the_address_of_a_local_in() {
1679 let (mut names, mut source, block, _) = blank(&[]);
1680 let slot = slot(&mut source, block, 4, 4);
1681 let mut build = Builder::new(&mut source, block);
1682 let nine = build.iconst(Type::int(32), 9);
1683 build.store(nine, slot, plain(), Flags::default());
1684 let loaded = build.load(Type::int(32), slot, plain(), Flags::default());
1685 build.ret(&[loaded]);
1686
1687 let lowered = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1688 let stack = lowered.stack;
1689 let mut out = lowered.func;
1690 let env = env();
1691 let allocation = rucc_regalloc::run(&mut out, &env);
1692 let layout = stack.layout(Layout::new(&SYSV, REGS));
1693 let frame = Frame::of(&out, &allocation, &layout);
1694 finish(&mut out, &allocation, &frame, &stack.addresses, &SYSV, &FRAME, &mut names);
1695
1696 let text = mir::print_func(&out, &names, ®S);
1701 assert!(text.contains("$rax = x64.lea_64 [$rsp - 8]"), "{text}");
1702 assert!(!text.contains("x64.sub_ri_64"), "{text}");
1703 assert_eq!(frame.size(), 0);
1704 assert_eq!(frame.local(0), Some(-8));
1705 }
1706
1707 #[test]
1708 fn a_stack_slot_whose_size_is_not_known_until_it_runs_is_reported() {
1709 let i64 = Type::int(64);
1710 let (mut names, mut source, block, args) = blank(&[i64]);
1711 let info = MemInfo { size: 0, align: 16, ..plain() };
1712 let mut build = Builder::new(&mut source, block);
1713 let mem = build.func().add_mem(info);
1714 let size = build.func().push_values(&[args[0]]);
1715 let slot = build.value(
1716 InstData { args: size, extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) },
1717 Type::PTR,
1718 );
1719 Builder::new(&mut source, block).ret(&[slot]);
1720
1721 let failed = func(&source, &mut names, &SYSV).expect_err("nothing grows the stack");
1725 assert_eq!(failed.to_string(), "nothing here grows the stack for a variable length array");
1726 }
1727
1728 #[test]
1729 fn an_address_is_read_written_and_added_to_like_the_integer_it_is() {
1730 let (mut names, mut source, block, args) = blank(&[Type::PTR, Type::int(64)]);
1731 let mut build = Builder::new(&mut source, block);
1732 let stepped = build.func().push_values(&[args[0], args[1]]);
1733 let next =
1734 build.value(InstData { args: stepped, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
1735 let loaded = build.load(Type::int(32), next, plain(), Flags::default());
1736 build.ret(&[loaded]);
1737
1738 assert_eq!(
1748 lower(&mut names, &source),
1749 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
1750 %1:gpr($rsi) = x64.arg_val_64\n %2:gpr(reuse 1) = x64.add_rr_64 %0, %1\n \
1751 %3:gpr = x64.mov_rm_32 [%2]\n x64.ret_val_32 %3($rax)\n}\n"
1752 );
1753 }
1754
1755 fn address_of(source: &mut Func, block: Block, names: &mut Interner, name: &str) -> Value {
1758 let symbol = names.intern(name);
1759 let mut build = Builder::new(source, block);
1760 build.value(
1761 InstData { extra: Extra::Symbol(symbol), ..InstData::new(Opcode::GlobalAddr) },
1762 Type::PTR,
1763 )
1764 }
1765
1766 #[test]
1767 fn the_address_of_a_name_is_one_instruction_carrying_the_name() {
1768 let (mut names, mut source, block, _) = blank(&[]);
1769 let counter = address_of(&mut source, block, &mut names, "counter");
1770 let mut build = Builder::new(&mut source, block);
1771 let loaded = build.load(Type::int(32), counter, plain(), Flags::default());
1772 build.ret(&[loaded]);
1773
1774 assert_eq!(
1778 lower(&mut names, &source),
1779 "mfunc @f {\nblock0:\n %0:gpr = x64.lea_64 [@counter]\n \
1780 %1:gpr = x64.mov_rm_32 [%0]\n x64.ret_val_32 %1($rax)\n}\n"
1781 );
1782 }
1783
1784 fn cast(source: &mut Func, block: Block, opcode: Opcode, from: Value, to: Type) -> Value {
1786 let mut build = Builder::new(source, block);
1787 let args = build.func().push_values(&[from]);
1788 build.value(InstData { args, ..InstData::new(opcode) }, to)
1789 }
1790
1791 #[test]
1792 fn a_cast_between_a_pointer_and_an_integer_as_wide_is_no_instruction_at_all() {
1793 let (mut names, mut source, block, args) = blank(&[Type::PTR]);
1794 let number = cast(&mut source, block, Opcode::PtrToInt, args[0], Type::int(64));
1795 Builder::new(&mut source, block).ret(&[number]);
1796
1797 assert_eq!(
1801 lower(&mut names, &source),
1802 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
1803 x64.ret_val_64 %0($rax)\n}\n"
1804 );
1805 }
1806
1807 #[test]
1808 fn a_null_pointer_is_a_constant_that_reaches_a_register_before_anything_reads_it() {
1809 let (mut names, mut source, block, _) = blank(&[]);
1810 let mut build = Builder::new(&mut source, block);
1811 let zero = build.iconst(Type::int(64), 0);
1812 let null = cast(&mut source, block, Opcode::IntToPtr, zero, Type::PTR);
1813 Builder::new(&mut source, block).ret(&[null]);
1814
1815 assert_eq!(
1819 lower(&mut names, &source),
1820 "mfunc @f {\nblock0:\n %0:gpr = x64.mov_ri_64 0\n x64.ret_val_64 %0($rax)\n}\n"
1821 );
1822 }
1823
1824 #[test]
1825 fn a_cast_between_a_pointer_and_a_narrower_integer_is_reported() {
1826 let (mut names, mut source, block, args) = blank(&[Type::PTR]);
1827 let number = cast(&mut source, block, Opcode::PtrToInt, args[0], Type::int(32));
1828 Builder::new(&mut source, block).ret(&[number]);
1829
1830 let failed = func(&source, &mut names, &SYSV).expect_err("no rule narrows an address");
1834 assert_eq!(failed.to_string(), "no rule lowers a `ptrtoint` producing a `i32`");
1835 }
1836}