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
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 Dynamic {
142 inst: Inst,
144 },
145}
146
147impl fmt::Display for Unsupported {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 match *self {
150 Unsupported::Inst { term: Some(term), .. } => write!(f, "no rule lowers `{term}`"),
151 Unsupported::Inst { term: None, .. } => f.write_str("no rule lowers this instruction"),
152 Unsupported::Argument { index, missing } => {
153 write!(f, "parameter {index} {}", missing.why())
154 }
155 Unsupported::Call { refused: Refused { argument: Some(index), missing }, .. } => {
156 write!(f, "argument {index} of this call {}", missing.why())
157 }
158 Unsupported::Call { refused: Refused { argument: None, missing }, .. } => {
159 write!(f, "what this call gives back {}", missing.why())
160 }
161 Unsupported::Indirect { .. } => f.write_str("no rule calls through an address"),
162 Unsupported::Dynamic { .. } => {
163 f.write_str("nothing here grows the stack for a variable length array")
164 }
165 }
166 }
167}
168
169impl std::error::Error for Unsupported {}
170
171#[derive(Debug)]
173pub struct Lowered {
174 pub func: mir::Func,
176 pub stack: Stack,
179}
180
181#[derive(Debug, Default)]
186pub struct Stack {
187 pub calls: Option<u32>,
193 pub locals: Vec<Local>,
196 pub addresses: Vec<(mir::Inst, usize)>,
202}
203
204impl Stack {
205 #[must_use]
210 pub fn layout<'a>(&'a self, base: Layout<'a>) -> Layout<'a> {
211 Layout {
212 leaf: self.calls.is_none(),
213 outgoing: self.calls.unwrap_or(0),
214 locals: &self.locals,
215 ..base
216 }
217 }
218}
219
220pub fn func(
228 source: &Func,
229 names: &mut Interner,
230 conv: &'static CallRegs,
231) -> Result<Lowered, Unsupported> {
232 Lowering::new(source, names, conv).run()
233}
234
235struct Lowering<'a> {
237 source: &'a Func,
238 names: &'a mut Interner,
239 out: mir::Func,
240 regs: Vec<Option<mir::Reg>>,
242 written: Vec<Option<mir::Block>>,
245 uses: Vec<u32>,
248 at: Option<mir::Block>,
250 blocks: Vec<Option<mir::Block>>,
252 gpr: RegClass,
254 conv: &'static CallRegs,
257 stack: Stack,
259}
260
261impl<'a> Lowering<'a> {
262 fn new(source: &'a Func, names: &'a mut Interner, conv: &'static CallRegs) -> Self {
263 let counts = source.counts();
264 let name = source.name;
265 let mut uses = vec![0; counts.values];
266 for block in source.blocks() {
267 for inst in source.insts(block) {
268 for &arg in &source[source[inst].args] {
269 uses[arg.index()] += 1;
270 }
271 for call in source.successors(inst) {
272 for &arg in &source[call.args] {
273 uses[arg.index()] += 1;
274 }
275 }
276 }
277 }
278 Self {
279 source,
280 names,
281 out: mir::Func::new(name),
282 regs: vec![None; counts.values],
283 written: vec![None; counts.values],
284 blocks: vec![None; counts.blocks],
285 uses,
286 at: None,
287 gpr: x86_64::GPR,
288 conv,
289 stack: Stack::default(),
290 }
291 }
292
293 fn run(mut self) -> Result<Lowered, Unsupported> {
294 for block in self.source.blocks() {
298 let out = self.out.create_block();
299 self.blocks[block.index()] = Some(out);
300 }
301 for block in self.source.blocks() {
302 self.block(block)?;
303 }
304 Ok(Lowered { func: self.out, stack: self.stack })
305 }
306
307 fn block(&mut self, block: Block) -> Result<(), Unsupported> {
309 let out = self.out_block(block);
310 self.at = Some(out);
311 if self.source.entry() == Some(block) {
312 self.arrive(block, out)?;
313 } else {
314 for ¶m in self.source[block].params.iter() {
315 let reg = self.out.append_param(out, self.gpr);
316 self.regs[param.index()] = Some(reg);
317 }
318 }
319
320 let insts: Vec<Inst> = self.source.insts(block).collect();
326 let mut found: Vec<Option<Match<Term>>> = (0..insts.len()).map(|_| None).collect();
327 let mut folded: Vec<Inst> = Vec::new();
328 for (index, &inst) in insts.iter().enumerate().rev() {
329 if folded.contains(&inst) {
330 continue;
331 }
332 if let Some((plan, matched)) = self.select(inst) {
333 folded.extend(self.folds(inst, plan));
334 found[index] = Some(matched);
335 }
336 }
337
338 for (&inst, matched) in insts.iter().zip(found) {
339 if folded.contains(&inst) || self.writes_nothing(inst) {
340 continue;
341 }
342 match self.source[inst].opcode {
347 Opcode::Call => {
348 self.called(inst)?;
349 continue;
350 }
351 Opcode::CallIndirect => return Err(Unsupported::Indirect { inst }),
352 Opcode::Alloca => {
357 self.reserve(inst)?;
358 continue;
359 }
360 _ => {}
361 }
362 let matched = matched.ok_or_else(|| self.unsupported(inst))?;
363 self.emit(inst, &matched)?;
364 }
365 self.edges(block, out)
366 }
367
368 fn called(&mut self, inst: Inst) -> Result<(), Unsupported> {
374 let data = &self.source[inst];
375 let Extra::Call(info) = data.extra else { return Err(self.unsupported(inst)) };
376 let info = self.source[info];
377 let Some(callee) = info.callee else { return Err(Unsupported::Indirect { inst }) };
378
379 let values: Vec<Value> = self.source[data.args].to_vec();
380 let mut args = Vec::with_capacity(values.len());
381 for value in values {
382 args.push((self.source[value].ty, self.reg_of(value)?));
383 }
384 let signature = &self.source[info.signature];
385 let variadic = signature.variadic;
386 let returns = signature.return_types().next();
387 if signature.return_types().count() > 1 {
390 return Err(self.unsupported(inst));
391 }
392
393 let block = self.at.expect("a block is being filled");
394 let what = abi::Calling { callee, args: &args, returns, variadic };
395 let made = abi::call(&mut self.out, block, &what, self.conv, self.names)
396 .map_err(|refused| Unsupported::Call { inst, refused })?;
397 let calls = &mut self.stack.calls;
398 *calls = Some(calls.unwrap_or(0).max(made.outgoing));
399 if let (Some(result), Some(reg)) = (data.first_result, made.result) {
400 self.regs[result.index()] = Some(reg);
401 }
402 Ok(())
403 }
404
405 fn reserve(&mut self, inst: Inst) -> Result<(), Unsupported> {
419 let data = &self.source[inst];
420 if !self.source[data.args].is_empty() {
423 return Err(Unsupported::Dynamic { inst });
424 }
425 let Extra::Mem(mem) = data.extra else { return Err(self.unsupported(inst)) };
426 let info = self.source[mem];
427 let size = u32::try_from(info.size).map_err(|_| Unsupported::Dynamic { inst })?;
428 let result = data.first_result.ok_or_else(|| self.unsupported(inst))?;
429
430 let index = self.stack.locals.len();
434 self.stack.locals.push(Local { size, align: info.align.max(1) });
435
436 let block = self.at.expect("a block is being filled");
437 let reg = self.new_reg(result);
438 let span = self.source.span(inst);
439 let lea = mir::Opcode::new(self.names.intern(&format!("{PREFIX}{}", x86_64::FRAME.lea)));
440 let sp = mir::Operand::read(mir::Reg::physical(self.conv.stack_pointer), self.gpr);
441 let made =
442 self.out.build(block, lea).at(span).def(reg, self.gpr).mem(mir::Mem::at(sp)).finish();
443 self.stack.addresses.push((made, index));
444 Ok(())
445 }
446
447 fn edges(&mut self, block: Block, out: mir::Block) -> Result<(), Unsupported> {
461 let Some(term) = self.source.terminator(block) else { return Ok(()) };
462 let branch =
463 if self.source[term].opcode == Opcode::BrIf { self.out.terminator(out) } else { None };
464
465 let calls: Vec<rucc_ir::BlockCall> = self.source.successors(term).collect();
466 let mut succs = Vec::with_capacity(calls.len());
467 for call in calls {
468 let args: Vec<Value> = self.source[call.args].to_vec();
469 let mut regs = Vec::with_capacity(args.len());
470 for value in args {
471 regs.push(self.reg_of(value)?);
472 }
473 succs.push(mir::BlockCall { block: self.out_block(call.block), args: regs });
474 }
475 if let Some(branch) = branch {
476 if self.out.terminator(out) != Some(branch) {
477 self.out.remove_inst(branch);
478 self.out.append_inst(out, branch);
479 }
480 }
481 *self.out.succs_mut(out) = succs;
482 Ok(())
483 }
484
485 fn out_block(&self, block: Block) -> mir::Block {
487 self.blocks[block.index()].expect("every block was created before any was filled")
488 }
489
490 fn arrive(&mut self, block: Block, out: mir::Block) -> Result<(), Unsupported> {
497 let params = self.source[block].params.clone();
498 let types: Vec<Type> = params.iter().map(|&value| self.source[value].ty).collect();
499 let regs = abi::entry(&mut self.out, out, &types, self.conv, self.names)
500 .map_err(|(index, missing)| Unsupported::Argument { index, missing })?;
501 for (¶m, reg) in params.iter().zip(regs) {
502 self.regs[param.index()] = Some(reg);
503 }
504 Ok(())
505 }
506
507 fn writes_nothing(&self, inst: Inst) -> bool {
519 let data = &self.source[inst];
520 match data.opcode {
521 Opcode::IConst | Opcode::Jump => true,
522 Opcode::Return => self.source[data.args].is_empty(),
523 _ => false,
524 }
525 }
526
527 fn select(&self, inst: Inst) -> Option<(Plan, Match<Term>)> {
533 for plan in self.plans(inst) {
534 let terms = Terms::new(self.source, inst, plan);
535 if let Some(matched) = TABLE.find(&terms, Term::Root) {
536 return Some((plan, matched));
537 }
538 }
539 None
540 }
541
542 fn plans(&self, inst: Inst) -> Vec<Plan> {
544 let args = &self.source[self.source[inst].args];
545 let mut plans = vec![PLAIN];
546 for (index, &arg) in args.iter().enumerate().take(MAX_ARGS) {
547 let mut ways = Vec::new();
548 if self.foldable(inst, arg) {
549 ways.push(Shown::Expand);
550 }
551 if Terms::new(self.source, inst, PLAIN).constant(arg).is_some() {
552 ways.push(Shown::Const);
553 }
554 ways.push(Shown::Reg);
555 plans = plans
556 .into_iter()
557 .flat_map(|plan| {
558 ways.iter().map(move |&way| {
559 let mut next = plan;
560 next[index] = way;
561 next
562 })
563 })
564 .collect();
565 }
566 plans
567 }
568
569 fn foldable(&self, into: Inst, value: Value) -> bool {
577 let Def::Result { inst, .. } = self.source[value].def else { return false };
578 if self.source[inst].opcode == Opcode::IConst || self.uses[value.index()] != 1 {
579 return false;
580 }
581 self.source.block_of(inst).is_some()
582 && self.source.block_of(inst) == self.source.block_of(into)
583 }
584
585 fn folds(&self, inst: Inst, plan: Plan) -> Vec<Inst> {
592 let args = &self.source[self.source[inst].args];
593 args.iter()
594 .take(MAX_ARGS)
595 .enumerate()
596 .filter(|&(index, _)| plan[index] == Shown::Expand)
597 .filter_map(|(_, &arg)| match self.source[arg].def {
598 Def::Result { inst, .. } => Some(inst),
599 Def::Param { .. } => None,
600 })
601 .collect()
602 }
603
604 fn emit(&mut self, inst: Inst, matched: &Match<Term>) -> Result<(), Unsupported> {
606 let rule: &Rule = TABLE.rule(matched);
607 let pieces = rule.replacement;
608 let Some(Piece::App { head, arity }) = pieces.first() else {
609 return Err(self.unsupported(inst));
610 };
611 let opcode = head.strip_prefix(PREFIX).ok_or_else(|| self.unsupported(inst))?;
612 let form = x86_64::form(opcode).ok_or_else(|| self.unsupported(inst))?;
613
614 let mut read = Read::default();
615 let mut at = 1;
616 for _ in 0..*arity {
617 at = self.read(inst, pieces, at, &matched.bindings, &mut read)?;
618 }
619
620 let descs = form.operands();
621 let writes = descs.iter().take_while(|desc| desc.role.is_def()).count();
622 if descs.len() - writes != read.regs.len() {
623 return Err(self.unsupported(inst));
624 }
625
626 let mut regs = Vec::new();
632 if writes > 0 {
633 let result = self.source[inst].first_result.ok_or_else(|| self.unsupported(inst))?;
634 regs.push(self.new_reg(result));
635 regs.extend((1..writes).map(|_| self.out.new_vreg(self.gpr)));
636 } else if self.source[inst].first_result.is_some() {
637 return Err(self.unsupported(inst));
640 }
641 regs.extend(read.regs.iter().copied());
642
643 let block = self.at.expect("a block is being filled");
644 let opcode = mir::Opcode::new(self.names.intern(head));
645 let mut build = self.out.build(block, opcode).at(self.source.span(inst));
646 for (desc, reg) in descs.iter().zip(regs) {
647 let operand = mir::Operand {
648 reg,
649 class: desc.class,
650 role: desc.role,
651 constraint: desc.constraint,
652 };
653 build = build.operand(operand);
654 }
655 if let Some(mem) = read.mem {
656 build = build.mem(mem);
657 }
658 if let Some(imm) = read.imm {
659 build = build.imm(imm);
660 }
661 build.finish();
662 Ok(())
663 }
664
665 fn read(
670 &mut self,
671 inst: Inst,
672 pieces: &'static [Piece],
673 at: usize,
674 bindings: &[Term],
675 out: &mut Read,
676 ) -> Result<usize, Unsupported> {
677 match pieces.get(at) {
678 Some(Piece::Int(value)) => {
679 out.imm = i64::try_from(*value).ok();
680 Ok(at + 1)
681 }
682 Some(Piece::Var { index, .. }) => {
683 match bindings.get(*index) {
684 Some(&Term::Reg(value)) => {
685 let reg = self.reg_of(value)?;
686 out.regs.push(reg);
687 }
688 Some(&Term::Num(value)) => out.imm = i64::try_from(value).ok(),
689 _ => return Err(self.unsupported(inst)),
692 }
693 Ok(at + 1)
694 }
695 Some(Piece::App { head, arity }) => {
696 let kind = x86_64::address(head).ok_or_else(|| self.unsupported(inst))?;
697 let mut inner = Read::default();
698 let mut next = at + 1;
699 for _ in 0..*arity {
700 next = self.read(inst, pieces, next, bindings, &mut inner)?;
701 }
702 let mem = address(kind, &inner, self.gpr).ok_or_else(|| self.unsupported(inst))?;
703 out.mem = Some(mem);
704 Ok(next)
705 }
706 None => Err(self.unsupported(inst)),
707 }
708 }
709
710 fn reg_of(&mut self, value: Value) -> Result<mir::Reg, Unsupported> {
723 let constant = match self.source[value].def {
724 Def::Result { inst, .. } => {
725 (self.source[inst].opcode == Opcode::IConst).then_some(inst)
726 }
727 Def::Param { .. } => None,
728 };
729 let here = self.at.expect("a block is being filled");
730 if let Some(reg) = self.regs[value.index()] {
731 if constant.is_none() || self.written[value.index()] == Some(here) {
732 return Ok(reg);
733 }
734 }
735 if let Some(inst) = constant {
736 self.regs[value.index()] = None;
739 let matched = self
740 .select(inst)
741 .map(|(_, matched)| matched)
742 .ok_or_else(|| self.unsupported(inst))?;
743 self.emit(inst, &matched)?;
744 self.written[value.index()] = Some(here);
745 return Ok(self.regs[value.index()].expect("a constant is written into a register"));
746 }
747 Ok(self.new_reg(value))
748 }
749
750 fn new_reg(&mut self, value: Value) -> mir::Reg {
752 if let Some(reg) = self.regs[value.index()] {
753 return reg;
754 }
755 let reg = self.out.new_vreg(self.gpr);
756 self.regs[value.index()] = Some(reg);
757 reg
758 }
759
760 fn unsupported(&self, inst: Inst) -> Unsupported {
761 Unsupported::Inst { inst, term: Terms::new(self.source, inst, PLAIN).name(inst) }
762 }
763}
764
765#[derive(Debug, Default)]
767struct Read {
768 regs: Vec<mir::Reg>,
769 imm: Option<i64>,
770 mem: Option<mir::Mem>,
771}
772
773fn address(kind: x86_64::Address, read: &Read, gpr: RegClass) -> Option<mir::Mem> {
779 let mut regs = read.regs.iter().copied().map(|reg| mir::Operand::read(reg, gpr));
780 match kind {
781 x86_64::Address::BaseIndexScale => {
782 let base = regs.next()?;
783 let index = regs.next()?;
784 Some(mir::Mem::at(base).indexed(index, u8::try_from(read.imm?).ok()?))
785 }
786 x86_64::Address::IndexScale => Some(mir::Mem {
787 base: None,
788 index: Some(regs.next()?),
789 scale: u8::try_from(read.imm?).ok()?,
790 disp: 0,
791 symbol: None,
792 }),
793 x86_64::Address::Base => Some(mir::Mem::at(regs.next()?)),
794 x86_64::Address::BaseOffset => {
797 Some(mir::Mem { disp: i32::try_from(read.imm?).ok()?, ..mir::Mem::at(regs.next()?) })
798 }
799 }
800}
801
802static TABLE: &Table = &crate::select::x86_64::TABLE;
808
809#[cfg(test)]
810mod tests {
811 use rucc_ir::{Builder, CallInfo, Flags, InstData, MemInfo, MemOrder, Signature, Type};
812 use rucc_regalloc::assign::Env;
813 use rucc_target::x86_64::{FRAME, REGS, SYSV};
814
815 use super::*;
816 use crate::finish::finish;
817 use crate::frame::{Frame, Layout};
818
819 fn blank(params: &[Type]) -> (Interner, Func, Block, Vec<Value>) {
821 let mut names = Interner::new();
822 let mut func = Func::new(names.intern("f"), Signature::new());
823 let block = func.create_block();
824 let values = params.iter().map(|&ty| func.append_param(block, ty)).collect();
825 (names, func, block, values)
826 }
827
828 fn plain() -> MemInfo {
831 MemInfo { size: 0, align: 1, order: MemOrder::NotAtomic, tbaa: None }
832 }
833
834 fn env() -> Env {
839 const SCRATCH: [rucc_target::PhysReg; 2] = [x86_64::R10, x86_64::R11];
840 let order: Vec<rucc_target::PhysReg> =
841 SYSV.int_order.iter().copied().filter(|reg| !SCRATCH.contains(reg)).collect();
842 Env::new().with(x86_64::GPR, &order, &SCRATCH)
843 }
844
845 fn lower(names: &mut Interner, source: &Func) -> String {
847 let out = func(source, names, &SYSV).expect("every instruction has a rule");
848 mir::print_func(&out.func, names, ®S)
849 }
850
851 #[test]
852 fn an_addition_of_two_registers_is_one_instruction() {
853 let i32 = Type::int(32);
854 let (mut names, mut func, block, args) = blank(&[i32, i32]);
855 let mut build = Builder::new(&mut func, block);
856 build.binary(Opcode::Add, args[0], args[1], Flags::default());
857
858 assert_eq!(
859 lower(&mut names, &func),
860 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
861 %1:gpr($rsi) = x64.arg_val_32\n %2:gpr(reuse 1) = x64.add_rr_32 %0, %1\n}\n"
862 );
863 }
864
865 #[test]
866 fn a_constant_operand_becomes_an_immediate() {
867 let i32 = Type::int(32);
868 let (mut names, mut func, block, args) = blank(&[i32]);
869 let mut build = Builder::new(&mut func, block);
870 let seven = build.iconst(i32, 7);
871 build.binary(Opcode::Add, args[0], seven, Flags::default());
872
873 assert_eq!(
876 lower(&mut names, &func),
877 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
878 %1:gpr(reuse 1) = x64.add_ri_32 %0, 7\n}\n"
879 );
880 }
881
882 #[test]
883 fn a_constant_too_wide_for_an_immediate_goes_into_a_register() {
884 let i64 = Type::int(64);
885 let (mut names, mut func, block, args) = blank(&[i64]);
886 let mut build = Builder::new(&mut func, block);
887 let big = build.iconst(i64, i128::from(i32::MAX) + 1);
888 build.binary(Opcode::Add, args[0], big, Flags::default());
889
890 assert_eq!(
894 lower(&mut names, &func),
895 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
896 %1:gpr = x64.mov_ri_64 2147483648\n %2:gpr(reuse 1) = x64.add_rr_64 %0, %1\n}\n"
897 );
898 }
899
900 #[test]
901 fn an_index_calculation_folds_into_an_address() {
902 let i64 = Type::int(64);
903 let (mut names, mut func, block, args) = blank(&[i64, i64]);
904 let mut build = Builder::new(&mut func, block);
905 let four = build.iconst(i64, 4);
906 let scaled = build.binary(Opcode::Mul, args[1], four, Flags::default());
907 build.binary(Opcode::Add, args[0], scaled, Flags::default());
908
909 assert_eq!(
912 lower(&mut names, &func),
913 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
914 %1:gpr($rsi) = x64.arg_val_64\n %2:gpr = x64.lea_64 [%0 + %1*4]\n}\n"
915 );
916 }
917
918 #[test]
919 fn an_instruction_read_twice_is_not_folded_into_either_reader() {
920 let i64 = Type::int(64);
921 let (mut names, mut func, block, args) = blank(&[i64, i64]);
922 let mut build = Builder::new(&mut func, block);
923 let four = build.iconst(i64, 4);
924 let scaled = build.binary(Opcode::Mul, args[1], four, Flags::default());
925 let first = build.binary(Opcode::Add, args[0], scaled, Flags::default());
926 build.binary(Opcode::Add, first, scaled, Flags::default());
927
928 let text = lower(&mut names, &func);
931 assert!(text.contains("x64.lea_64 [%1*4]"), "{text}");
932 assert_eq!(text.matches("x64.add_rr_64").count(), 2, "{text}");
933 }
934
935 #[test]
936 fn a_shift_by_a_register_asks_for_it_in_cl() {
937 let i32 = Type::int(32);
938 let (mut names, mut func, block, args) = blank(&[i32, i32]);
939 let mut build = Builder::new(&mut func, block);
940 build.binary(Opcode::Shl, args[0], args[1], Flags::default());
941
942 let text = lower(&mut names, &func);
945 assert!(text.contains("x64.shl_rcl_32 %0, %1($rcx)"), "{text}");
946 }
947
948 #[test]
949 fn a_division_names_the_registers_and_the_register_it_destroys() {
950 let i32 = Type::int(32);
951 let (mut names, mut func, block, args) = blank(&[i32, i32]);
952 let mut build = Builder::new(&mut func, block);
953 build.binary(Opcode::SDiv, args[0], args[1], Flags::default());
954
955 let text = lower(&mut names, &func);
958 assert!(
959 text.contains("%2:gpr($rax), early %3:gpr($rdx) = x64.idiv_quo_32 %0($rax), %1"),
960 "{text}"
961 );
962 }
963
964 #[test]
965 fn a_load_reads_through_the_register_the_address_is_in() {
966 let i64 = Type::int(64);
967 let (mut names, mut func, block, args) = blank(&[i64]);
968 let mut build = Builder::new(&mut func, block);
969 build.load(Type::int(32), args[0], plain(), Flags::default());
970
971 assert_eq!(
972 lower(&mut names, &func),
973 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
974 %1:gpr = x64.mov_rm_32 [%0]\n}\n"
975 );
976 }
977
978 #[test]
979 fn a_store_writes_no_register_and_the_value_it_writes_is_the_one_the_ir_gave_it() {
980 let (mut names, mut func, block, args) = blank(&[Type::int(32), Type::int(64)]);
981 let mut build = Builder::new(&mut func, block);
982 build.store(args[0], args[1], plain(), Flags::default());
983
984 assert_eq!(
988 lower(&mut names, &func),
989 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
990 %1:gpr($rsi) = x64.arg_val_64\n x64.mov_mr_32 %0, [%1]\n}\n"
991 );
992 }
993
994 #[test]
995 fn an_address_with_a_constant_added_folds_into_the_access() {
996 let i64 = Type::int(64);
997 let (mut names, mut func, block, args) = blank(&[i64]);
998 let mut build = Builder::new(&mut func, block);
999 let twelve = build.iconst(i64, 12);
1000 let field = build.binary(Opcode::Add, args[0], twelve, Flags::default());
1001 build.load(Type::int(64), field, plain(), Flags::default());
1002
1003 assert_eq!(
1006 lower(&mut names, &func),
1007 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
1008 %1:gpr = x64.mov_rm_64 [%0 + 12]\n}\n"
1009 );
1010 }
1011
1012 #[test]
1013 fn a_displacement_too_wide_to_encode_leaves_the_addition_where_it_is() {
1014 let i64 = Type::int(64);
1015 let (mut names, mut func, block, args) = blank(&[i64]);
1016 let mut build = Builder::new(&mut func, block);
1017 let big = build.iconst(i64, i128::from(i32::MAX) + 1);
1018 let far = build.binary(Opcode::Add, args[0], big, Flags::default());
1019 build.load(Type::int(32), far, plain(), Flags::default());
1020
1021 let text = lower(&mut names, &func);
1025 assert!(text.contains("x64.mov_rm_32 [%2]"), "{text}");
1026 assert!(text.contains("x64.add_rr_64"), "{text}");
1027 }
1028
1029 #[test]
1030 fn a_store_of_a_value_that_was_loaded_is_two_instructions_and_no_arithmetic() {
1031 let i64 = Type::int(64);
1032 let (mut names, mut func, block, args) = blank(&[i64, i64]);
1033 let mut build = Builder::new(&mut func, block);
1034 let got = build.load(Type::int(8), args[0], plain(), Flags::default());
1035 build.store(got, args[1], plain(), Flags::default());
1036
1037 assert_eq!(
1041 lower(&mut names, &func),
1042 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
1043 %1:gpr($rsi) = x64.arg_val_64\n %2:gpr = x64.mov_rm_8 [%0]\n \
1044 x64.mov_mr_8 %2, [%1]\n}\n"
1045 );
1046 }
1047
1048 #[test]
1049 fn an_access_at_a_width_no_rule_is_written_at_is_reported() {
1050 let i64 = Type::int(64);
1051 let (mut names, mut source, block, args) = blank(&[i64]);
1052 let mut build = Builder::new(&mut source, block);
1053 build.load(Type::int(128), args[0], plain(), Flags::default());
1054
1055 let failed = func(&source, &mut names, &SYSV).expect_err("nothing loads 128 bits");
1056 assert_eq!(failed.to_string(), "no rule lowers this instruction");
1057 }
1058
1059 #[test]
1060 fn a_return_asks_for_the_value_in_the_register_the_caller_reads() {
1061 let (mut names, mut func, block, args) = blank(&[Type::int(32)]);
1062 let mut build = Builder::new(&mut func, block);
1063 build.ret(&[args[0]]);
1064
1065 assert_eq!(
1070 lower(&mut names, &func),
1071 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
1072 x64.ret_val_32 %0($rax)\n}\n"
1073 );
1074 }
1075
1076 #[test]
1077 fn a_return_of_a_constant_puts_it_in_a_register_first() {
1078 let (mut names, mut func, block, _) = blank(&[]);
1079 let mut build = Builder::new(&mut func, block);
1080 let zero = build.iconst(Type::int(32), 0);
1081 build.ret(&[zero]);
1082
1083 assert_eq!(
1087 lower(&mut names, &func),
1088 "mfunc @f {\nblock0:\n %0:gpr = x64.mov_ri_32 0\n x64.ret_val_32 %0($rax)\n}\n"
1089 );
1090 }
1091
1092 #[test]
1093 fn a_return_of_nothing_is_no_instruction_at_all() {
1094 let (mut names, mut func, block, _) = blank(&[]);
1095 let mut build = Builder::new(&mut func, block);
1096 build.ret(&[]);
1097
1098 assert_eq!(lower(&mut names, &func), "mfunc @f {\nblock0:\n}\n");
1102 }
1103
1104 #[test]
1105 fn the_allocator_is_what_moves_the_answer_into_the_return_register() {
1106 let (mut names, mut source, block, _) = blank(&[]);
1107 let mut build = Builder::new(&mut source, block);
1108 let zero = build.iconst(Type::int(32), 0);
1109 build.ret(&[zero]);
1110
1111 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1112 let env = env();
1113 let allocation = rucc_regalloc::run(&mut out, &env);
1114 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
1115 finish(&mut out, &allocation, &frame, &[], &SYSV, &FRAME, &mut names);
1116
1117 assert_eq!(
1128 mir::print_func(&out, &names, ®S),
1129 "mfunc @f {\nblock0:\n $rcx = x64.mov_ri_32 0\n $rax = x64.mov_rr_64 $rcx\n \
1130 x64.ret_val_32 $rax($rax)\n x64.ret\n}\n"
1131 );
1132 }
1133
1134 #[test]
1135 fn a_function_of_two_arguments_is_a_whole_function_now() {
1136 let i32 = Type::int(32);
1137 let (mut names, mut source, block, args) = blank(&[i32, i32]);
1138 let mut build = Builder::new(&mut source, block);
1139 let sum = build.binary(Opcode::Add, args[0], args[1], Flags::default());
1140 build.ret(&[sum]);
1141
1142 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1143 let env = env();
1144 let allocation = rucc_regalloc::run(&mut out, &env);
1145 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
1146 finish(&mut out, &allocation, &frame, &[], &SYSV, &FRAME, &mut names);
1147
1148 assert_eq!(
1164 mir::print_func(&out, &names, ®S),
1165 "mfunc @f {\nblock0:\n $rdi($rdi) = x64.arg_val_32\n \
1166 $rax = x64.mov_rr_64 $rdi\n $rsi($rsi) = x64.arg_val_32\n \
1167 $rcx = x64.mov_rr_64 $rsi\n $rdx = x64.mov_rr_64 $rax\n \
1168 $rdx(reuse 1) = x64.add_rr_32 $rax, $rcx\n $rax = x64.mov_rr_64 $rdx\n \
1169 x64.ret_val_32 $rax($rax)\n x64.ret\n}\n"
1170 );
1171 }
1172
1173 #[test]
1174 fn an_argument_with_no_register_left_for_it_is_reported() {
1175 let i64 = Type::int(64);
1176 let (mut names, mut source, block, args) = blank(&[i64; 7]);
1177 let mut build = Builder::new(&mut source, block);
1178 build.ret(&[args[6]]);
1179
1180 let failed = func(&source, &mut names, &SYSV).expect_err("the seventh is on the stack");
1184 assert_eq!(failed.to_string(), "parameter 6 is passed on the stack");
1185 }
1186
1187 #[test]
1188 fn a_jump_is_the_edge_and_nothing_else() {
1189 let i32 = Type::int(32);
1190 let (mut names, mut source, entry, args) = blank(&[i32]);
1191 let next = source.create_block();
1192 let got = source.append_param(next, i32);
1193 Builder::new(&mut source, entry).jump(next, &[args[0]]);
1194 Builder::new(&mut source, next).ret(&[got]);
1195
1196 assert_eq!(
1199 lower(&mut names, &source),
1200 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32 block1(%0)\n\n\
1201 block1(%1:gpr):\n x64.ret_val_32 %1($rax)\n}\n"
1202 );
1203 }
1204
1205 #[test]
1211 fn a_constant_two_blocks_want_is_written_in_both_of_them() {
1212 let i32 = Type::int(32);
1213 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1214 let then = source.create_block();
1215 let other = source.create_block();
1216 let join = source.create_block();
1217 let got = source.append_param(join, i32);
1218
1219 let mut build = Builder::new(&mut source, entry);
1220 let seven = build.iconst(i32, 7);
1221 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1222 build.br_if(cond, then, &[], other, &[]);
1223 Builder::new(&mut source, then).jump(join, &[seven]);
1226 Builder::new(&mut source, other).jump(join, &[seven]);
1227 Builder::new(&mut source, join).ret(&[got]);
1228
1229 let text = lower(&mut names, &source);
1230 assert_eq!(text.matches("x64.mov_ri_32 7").count(), 2, "one seven per block: {text}");
1231 }
1232
1233 #[test]
1237 fn a_constant_an_edge_wants_is_written_before_the_branch_and_not_after_it() {
1238 let i32 = Type::int(32);
1239 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1240 let then = source.create_block();
1241 let join = source.create_block();
1242 let got = source.append_param(join, i32);
1243
1244 let mut build = Builder::new(&mut source, entry);
1245 let nine = build.iconst(i32, 9);
1246 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1247 build.br_if(cond, then, &[], join, &[nine]);
1248 Builder::new(&mut source, then).jump(join, &[args[0]]);
1249 Builder::new(&mut source, join).ret(&[got]);
1250
1251 let out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1252 let entry = out.entry().expect("an entry block");
1253 let last = out.terminator(entry).expect("a block that leaves two ways has a branch");
1254 let branch = names.intern("x64.br_cond_8");
1255 assert_eq!(
1256 out[last].opcode,
1257 mir::Opcode::new(branch),
1258 "the branch is last: {}",
1259 mir::print_func(&out, &names, ®S)
1260 );
1261 }
1262
1263 #[test]
1264 fn a_conditional_branch_is_lowered_to_the_condition_and_nothing_about_where_it_goes() {
1265 let i32 = Type::int(32);
1266 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1267 let then = source.create_block();
1268 let other = source.create_block();
1269 let mut build = Builder::new(&mut source, entry);
1270 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1271 build.br_if(cond, then, &[], other, &[]);
1272 Builder::new(&mut source, then).ret(&[args[0]]);
1273 Builder::new(&mut source, other).ret(&[args[1]]);
1274
1275 assert_eq!(
1279 lower(&mut names, &source),
1280 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_32\n \
1281 %1:gpr($rsi) = x64.arg_val_32\n %2:gpr = x64.cmp_set_l_32 %0, %1\n \
1282 x64.br_cond_8 %2, block1, block2\n\n\
1283 block1:\n x64.ret_val_32 %0($rax)\n\n\
1284 block2:\n x64.ret_val_32 %1($rax)\n}\n"
1285 );
1286 }
1287
1288 #[test]
1289 fn a_branch_over_a_block_is_a_whole_function_now() {
1290 let i32 = Type::int(32);
1291 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1292 let then = source.create_block();
1293 let other = source.create_block();
1294 let join = source.create_block();
1295 let got = source.append_param(join, i32);
1296 let mut build = Builder::new(&mut source, entry);
1297 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1298 build.br_if(cond, then, &[], other, &[]);
1299 let mut build = Builder::new(&mut source, then);
1300 let sum = build.binary(Opcode::Add, args[0], args[1], Flags::default());
1301 build.jump(join, &[sum]);
1302 Builder::new(&mut source, other).jump(join, &[args[1]]);
1303 Builder::new(&mut source, join).ret(&[got]);
1304
1305 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1311 assert_eq!(crate::split::critical(&mut out), 0, "no edge here is critical");
1312 let env = env();
1313 let allocation = rucc_regalloc::run(&mut out, &env);
1314 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
1315 finish(&mut out, &allocation, &frame, &[], &SYSV, &FRAME, &mut names);
1316
1317 let text = mir::print_func(&out, &names, ®S);
1322 assert_eq!(text.matches("x64.ret\n").count(), 1, "{text}");
1323 assert!(text.contains("x64.br_cond_8"), "{text}");
1324 assert!(text.contains("x64.add_rr_32"), "{text}");
1325 assert!(!text.contains('%'), "{text}");
1326 }
1327
1328 #[test]
1329 fn a_critical_edge_is_split_before_the_allocator_ever_sees_it() {
1330 let i32 = Type::int(32);
1331 let (mut names, mut source, entry, args) = blank(&[i32, i32]);
1332 let then = source.create_block();
1333 let join = source.create_block();
1334 let got = source.append_param(join, i32);
1335 let mut build = Builder::new(&mut source, entry);
1336 let cond = build.icmp(rucc_ir::IntPred::Slt, args[0], args[1]);
1337 build.br_if(cond, then, &[], join, &[args[1]]);
1338 Builder::new(&mut source, then).jump(join, &[args[0]]);
1339 let mut build = Builder::new(&mut source, join);
1340 let twice = build.binary(Opcode::Add, got, got, Flags::default());
1341 build.ret(&[twice]);
1342
1343 let mut out = func(&source, &mut names, &SYSV).expect("every instruction has a rule").func;
1348 assert_eq!(crate::split::critical(&mut out), 1);
1349 let env = env();
1350 let allocation = rucc_regalloc::run(&mut out, &env);
1351 let frame = Frame::of(&out, &allocation, &Layout::new(&SYSV, REGS));
1352 finish(&mut out, &allocation, &frame, &[], &SYSV, &FRAME, &mut names);
1353
1354 let text = mir::print_func(&out, &names, ®S);
1356 assert_eq!(out.block_count(), 4, "{text}");
1357 assert_eq!(text.matches("x64.ret\n").count(), 1, "{text}");
1358 }
1359
1360 #[test]
1361 fn a_call_passes_what_the_convention_says_and_takes_back_what_it_says() {
1362 let i32 = Type::int(32);
1363 let (mut names, mut source, block, args) = blank(&[i32, i32]);
1364 let sig =
1365 source.add_signature(Signature::new().with_params(&[i32, i32]).with_returns(&[i32]));
1366 let callee = names.intern("g");
1367 let call = Builder::new(&mut source, block).call(callee, sig, &[args[0], args[1]]);
1368 let got = source[call].first_result.expect("an integer comes back");
1369 Builder::new(&mut source, block).ret(&[got]);
1370
1371 let text = lower(&mut names, &source);
1375 assert!(text.contains("= x64.call %0($rdi), %1($rsi), @g"), "{text}");
1376 assert!(text.contains("x64.ret_val_32 %2($rax)"), "{text}");
1377 assert!(text.contains("%2:gpr($rax), $rcx, $rdx, $r8, $r9, $r10, $r11, $xmm0,"), "{text}");
1381 assert!(text.contains("$xmm15 = x64.call"), "{text}");
1382 }
1383
1384 #[test]
1385 fn what_the_frame_owes_a_call_comes_back_with_the_function() {
1386 let i32 = Type::int(32);
1387 let sig = |source: &mut Func| source.add_signature(Signature::new().with_params(&[i32]));
1388
1389 let (mut names, mut source, block, args) = blank(&[i32]);
1390 let sig = sig(&mut source);
1391 let callee = names.intern("g");
1392 Builder::new(&mut source, block).call(callee, sig, &[args[0]]);
1393 let out = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1394
1395 assert_eq!(out.stack.calls, Some(0));
1398 let layout = out.stack.layout(Layout::new(&SYSV, REGS));
1399 assert!(!layout.leaf);
1400 assert_eq!(layout.outgoing, 0);
1401
1402 let out = func(&source, &mut names, &x86_64::WIN64).expect("every instruction has a rule");
1405 assert_eq!(out.stack.calls, Some(32));
1406
1407 let (mut names, mut source, block, args) = blank(&[i32]);
1409 Builder::new(&mut source, block).ret(&[args[0]]);
1410 let out = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1411 assert_eq!(out.stack.calls, None);
1412 assert!(out.stack.layout(Layout::new(&SYSV, REGS)).leaf);
1413 }
1414
1415 #[test]
1416 fn a_value_that_outlives_a_call_is_not_left_where_the_call_destroys_it() {
1417 let i32 = Type::int(32);
1418 let (mut names, mut source, block, args) = blank(&[i32]);
1419 let sig = source.add_signature(Signature::new().with_params(&[i32]).with_returns(&[i32]));
1420 let callee = names.intern("g");
1421 let call = Builder::new(&mut source, block).call(callee, sig, &[args[0]]);
1422 let got = source[call].first_result.expect("an integer comes back");
1423 let mut build = Builder::new(&mut source, block);
1424 let sum = build.binary(Opcode::Add, got, args[0], Flags::default());
1425 build.ret(&[sum]);
1426
1427 let lowered = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1430 let layout = lowered.stack.layout(Layout::new(&SYSV, REGS));
1431 let mut out = lowered.func;
1432 let env = env();
1433 let allocation = rucc_regalloc::run(&mut out, &env);
1434 let frame = Frame::of(&out, &allocation, &layout);
1435 finish(&mut out, &allocation, &frame, &[], &SYSV, &FRAME, &mut names);
1436
1437 let text = mir::print_func(&out, &names, ®S);
1440 assert!(text.contains("$rbx"), "{text}");
1441 assert!(!text.contains('%'), "{text}");
1442 assert_eq!(text.matches("x64.call").count(), 1, "{text}");
1443 }
1444
1445 #[test]
1446 fn a_call_this_cannot_make_is_reported_rather_than_made() {
1447 let i64 = Type::int(64);
1448 let (mut names, mut source, block, args) = blank(&[i64]);
1449 let seven = vec![i64; 7];
1450 let sig = source.add_signature(Signature::new().with_params(&seven));
1451 let callee = names.intern("g");
1452 let passed = vec![args[0]; 7];
1453 Builder::new(&mut source, block).call(callee, sig, &passed);
1454
1455 let failed = func(&source, &mut names, &SYSV).expect_err("the seventh is on the stack");
1458 assert_eq!(failed.to_string(), "argument 6 of this call is passed on the stack");
1459
1460 let (mut names, mut source, block, _) = blank(&[]);
1461 let sig = source
1462 .add_signature(Signature::new().with_returns(&[Type::float(rucc_ir::Float::F64)]));
1463 let callee = names.intern("g");
1464 Builder::new(&mut source, block).call(callee, sig, &[]);
1465 let failed = func(&source, &mut names, &SYSV).expect_err("a double comes back in xmm0");
1466 assert_eq!(failed.to_string(), "what this call gives back is in a vector register");
1467 }
1468
1469 #[test]
1470 fn a_call_through_an_address_is_reported_as_one() {
1471 let i32 = Type::int(32);
1472 let (mut names, mut source, block, args) = blank(&[i32]);
1473 let sig = source.add_signature(Signature::new().with_params(&[i32]));
1474 let varargs = source.push_abis(&[]);
1475 let info = source.add_call(CallInfo { callee: None, signature: sig, varargs });
1476 let mut build = Builder::new(&mut source, block);
1477 let inst = InstData {
1478 args: build.func().push_values(&[args[0], args[0]]),
1479 extra: Extra::Call(info),
1480 ..InstData::new(Opcode::CallIndirect)
1481 };
1482 build.inst(inst, &[]);
1483
1484 let failed = func(&source, &mut names, &SYSV).expect_err("nothing calls through a value");
1487 assert_eq!(failed.to_string(), "no rule calls through an address");
1488 }
1489
1490 #[test]
1491 fn an_instruction_no_rule_covers_is_reported() {
1492 let i64 = Type::int(64);
1493 let (mut names, mut source, block, args) = blank(&[i64, i64]);
1494 let mut build = Builder::new(&mut source, block);
1495 build.ret(&[args[0], args[1]]);
1496
1497 let failed = func(&source, &mut names, &SYSV).expect_err("nothing returns two values");
1500 assert_eq!(failed.to_string(), "no rule lowers this instruction");
1501 }
1502
1503 fn slot(source: &mut Func, block: Block, size: u64, align: u32) -> Value {
1505 let info = MemInfo { size, align, ..plain() };
1506 let mut build = Builder::new(source, block);
1507 let mem = build.func().add_mem(info);
1508 build.value(InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) }, Type::PTR)
1509 }
1510
1511 #[test]
1512 fn a_local_is_memory_in_the_frame_and_one_instruction_that_says_where() {
1513 let (mut names, mut source, block, _) = blank(&[]);
1514 let slot = slot(&mut source, block, 4, 4);
1515 let mut build = Builder::new(&mut source, block);
1516 let nine = build.iconst(Type::int(32), 9);
1517 build.store(nine, slot, plain(), Flags::default());
1518 let loaded = build.load(Type::int(32), slot, plain(), Flags::default());
1519 build.ret(&[loaded]);
1520
1521 let lowered = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1522
1523 assert_eq!(lowered.stack.locals, vec![Local { size: 4, align: 4 }]);
1527 assert_eq!(lowered.stack.addresses.len(), 1);
1528 assert_eq!(lowered.stack.addresses[0].1, 0);
1529 assert_eq!(
1530 mir::print_func(&lowered.func, &names, ®S),
1531 "mfunc @f {\nblock0:\n %0:gpr = x64.lea_64 [$rsp]\n \
1532 %1:gpr = x64.mov_ri_32 9\n x64.mov_mr_32 %1, [%0]\n \
1533 %2:gpr = x64.mov_rm_32 [%0]\n x64.ret_val_32 %2($rax)\n}\n"
1534 );
1535 }
1536
1537 #[test]
1538 fn the_frame_is_what_fills_the_address_of_a_local_in() {
1539 let (mut names, mut source, block, _) = blank(&[]);
1540 let slot = slot(&mut source, block, 4, 4);
1541 let mut build = Builder::new(&mut source, block);
1542 let nine = build.iconst(Type::int(32), 9);
1543 build.store(nine, slot, plain(), Flags::default());
1544 let loaded = build.load(Type::int(32), slot, plain(), Flags::default());
1545 build.ret(&[loaded]);
1546
1547 let lowered = func(&source, &mut names, &SYSV).expect("every instruction has a rule");
1548 let stack = lowered.stack;
1549 let mut out = lowered.func;
1550 let env = env();
1551 let allocation = rucc_regalloc::run(&mut out, &env);
1552 let layout = stack.layout(Layout::new(&SYSV, REGS));
1553 let frame = Frame::of(&out, &allocation, &layout);
1554 finish(&mut out, &allocation, &frame, &stack.addresses, &SYSV, &FRAME, &mut names);
1555
1556 let text = mir::print_func(&out, &names, ®S);
1561 assert!(text.contains("$rax = x64.lea_64 [$rsp - 8]"), "{text}");
1562 assert!(!text.contains("x64.sub_ri_64"), "{text}");
1563 assert_eq!(frame.size(), 0);
1564 assert_eq!(frame.local(0), Some(-8));
1565 }
1566
1567 #[test]
1568 fn a_stack_slot_whose_size_is_not_known_until_it_runs_is_reported() {
1569 let i64 = Type::int(64);
1570 let (mut names, mut source, block, args) = blank(&[i64]);
1571 let info = MemInfo { size: 0, align: 16, ..plain() };
1572 let mut build = Builder::new(&mut source, block);
1573 let mem = build.func().add_mem(info);
1574 let size = build.func().push_values(&[args[0]]);
1575 let slot = build.value(
1576 InstData { args: size, extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) },
1577 Type::PTR,
1578 );
1579 Builder::new(&mut source, block).ret(&[slot]);
1580
1581 let failed = func(&source, &mut names, &SYSV).expect_err("nothing grows the stack");
1585 assert_eq!(failed.to_string(), "nothing here grows the stack for a variable length array");
1586 }
1587
1588 #[test]
1589 fn an_address_is_read_written_and_added_to_like_the_integer_it_is() {
1590 let (mut names, mut source, block, args) = blank(&[Type::PTR, Type::int(64)]);
1591 let mut build = Builder::new(&mut source, block);
1592 let stepped = build.func().push_values(&[args[0], args[1]]);
1593 let next =
1594 build.value(InstData { args: stepped, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
1595 let loaded = build.load(Type::int(32), next, plain(), Flags::default());
1596 build.ret(&[loaded]);
1597
1598 assert_eq!(
1608 lower(&mut names, &source),
1609 "mfunc @f {\nblock0:\n %0:gpr($rdi) = x64.arg_val_64\n \
1610 %1:gpr($rsi) = x64.arg_val_64\n %2:gpr(reuse 1) = x64.add_rr_64 %0, %1\n \
1611 %3:gpr = x64.mov_rm_32 [%2]\n x64.ret_val_32 %3($rax)\n}\n"
1612 );
1613 }
1614}