1use std::fmt::Write as _;
47
48use rucc_base::Interner;
49use rucc_mir::{Amode, Block, CfiOp, Func, Inst, Opcode, Operand, Reach, defs};
50use rucc_object::{Alias, FUNC_ALIGN, Output, Sections};
51use rucc_target::x86_64::{self, Arg, Width};
52use rucc_target::{PhysReg, RegClass, Segment, TargetInfo, aarch64};
53use rucc_tuple::Arch;
54
55use crate::Error;
56use crate::a64;
57use crate::data::{Globals, Piece, Variable};
58use crate::format::{Directives, binding, visibility};
59
60const PREFIX: &str = "x64.";
66
67pub fn print(
86 funcs: &[Func],
87 globals: &Globals,
88 aliases: &[Alias],
89 names: &Interner,
90 target: &TargetInfo,
91 unwind: bool,
92 output: Output,
93) -> Result<String, Error> {
94 let Output { sections, property } = output;
95 let arch = target.tuple.arch();
96 if !matches!(arch, Arch::X86_64 | Arch::Aarch64) {
97 return Err(Error::Machine { triple: target.tuple.to_string() });
98 }
99 let directives = Directives::of(target.object_format);
100 let mut writer = Writer {
101 arch,
102 names,
103 directives,
104 unwind: unwind && directives == Directives::Elf,
107 out: String::new(),
108 labels: Vec::new(),
109 sections,
110 };
111 writer.out.push_str(writer.directives.text());
112 writer.out.push('\n');
113 for func in funcs {
114 writer.func(func)?;
115 }
116 for var in &globals.vars {
117 writer.variable(var);
118 }
119 for alias in aliases {
120 writer.directives.alias(&mut writer.out, alias);
121 }
122 for name in &globals.weak {
125 writer.directives.absent(&mut writer.out, name);
126 }
127 writer.directives.end(&mut writer.out, property);
128 Ok(writer.out)
129}
130
131pub(crate) fn template(
138 func: &Func,
139 block: Block,
140 inst: Inst,
141 names: &Interner,
142 directives: Directives,
143) -> Result<String, Error> {
144 let mut writer = Writer {
145 arch: Arch::X86_64,
146 names,
147 directives,
148 unwind: false,
149 out: String::new(),
150 labels: Vec::new(),
151 sections: Sections::default(),
152 };
153 writer.inst(func, block, inst, names.resolve(func.name))?;
154 Ok(writer.out)
155}
156
157struct Writer<'a> {
159 arch: Arch,
162 names: &'a Interner,
163 directives: Directives,
164 unwind: bool,
166 out: String,
167 labels: Vec<u32>,
170 sections: Sections,
172}
173
174impl Writer<'_> {
175 fn func(&mut self, func: &Func) -> Result<(), Error> {
177 let name = self.names.resolve(func.name).to_owned();
178 self.number(func);
179 let binding = binding(func.binding);
180 let seen = visibility(func.visibility);
181 let align = func.align.unwrap_or(FUNC_ALIGN);
182 self.directives.code(&mut self.out, &name, self.sections);
183 let patch =
187 func.patch.map(|patch| (patch, format!("{}pfe_{name}", self.directives.local())));
188 let mut ahead = String::new();
189 if let Some((patch, label)) = &patch {
190 let back = if self.sections.functions {
191 format!("\t.section\t.text.{name}")
192 } else {
193 self.directives.text().to_owned()
194 };
195 self.directives.patchable(&mut ahead, label, &back);
196 if patch.before > 0 {
197 let _ = writeln!(ahead, "{label}:");
198 self.pad(&mut ahead, patch.pad, patch.before);
199 }
200 }
201 let fill = self.fill();
202 self.directives.open(&mut self.out, &name, align, fill, binding, seen, &ahead);
203 let unwind = self.unwind;
204 if unwind {
205 let _ = writeln!(self.out, "\t.cfi_startproc");
206 }
207 let end = func.cfi_end();
208 let loops = crate::bytes::loop_sizes(self.names, self.directives, func).unwrap_or_default();
212 for (index, block) in func.blocks().enumerate() {
213 let size = loops.get(block.index()).copied().unwrap_or(0);
214 if let Some(most) = crate::loop_room(size) {
215 let _ = writeln!(self.out, "\t.p2align\t6,,{most}");
216 }
217 let _ = writeln!(self.out, "{}{name}_{index}:", self.directives.local());
218 if let Some(label) = func.block_name(block) {
223 let _ = writeln!(self.out, "{}:", self.names.resolve(label));
224 }
225 for inst in func.insts(block) {
226 if let Some((patch, label)) = &patch {
232 if patch.before == 0 && patch.after == Some(inst) {
233 let _ = writeln!(self.out, "{label}:");
234 }
235 }
236 self.inst(func, block, inst, &name)?;
237 if unwind && Some(inst) != end {
238 for op in func.cfi_after(inst) {
239 self.cfi(op);
240 }
241 }
242 }
243 }
244 self.tables(func, &name);
245 if unwind {
246 let _ = writeln!(self.out, "\t.cfi_endproc");
247 }
248 self.directives.close(&mut self.out, &name);
249 Ok(())
250 }
251
252 fn pad(&self, out: &mut String, pad: Opcode, count: u32) {
259 let spelled = self.names.resolve(pad.name());
260 let opcode = spelled.strip_prefix(self.prefix()).unwrap_or(spelled);
261 for _ in 0..count {
262 let _ = writeln!(out, "\t{opcode}");
263 }
264 }
265
266 fn cfi(&mut self, op: CfiOp) {
273 let _ = match op {
274 CfiOp::DefCfa { reg, offset } => {
275 writeln!(self.out, "\t.cfi_def_cfa {reg}, {offset}")
276 }
277 CfiOp::DefCfaOffset(offset) => writeln!(self.out, "\t.cfi_def_cfa_offset {offset}"),
278 CfiOp::DefCfaRegister(reg) => writeln!(self.out, "\t.cfi_def_cfa_register {reg}"),
279 CfiOp::Offset { reg, offset } => writeln!(self.out, "\t.cfi_offset {reg}, {offset}"),
280 CfiOp::Restore(reg) => writeln!(self.out, "\t.cfi_restore {reg}"),
281 CfiOp::RememberState => writeln!(self.out, "\t.cfi_remember_state"),
282 CfiOp::RestoreState => writeln!(self.out, "\t.cfi_restore_state"),
283 };
284 }
285
286 fn variable(&mut self, var: &Variable) {
288 if !self.directives.variable(&mut self.out, var, self.sections) {
289 return;
290 }
291 for piece in &var.pieces {
292 self.piece(piece);
293 }
294 self.directives.close(&mut self.out, &var.name);
295 self.directives.descriptor(&mut self.out, var);
296 }
297
298 fn piece(&mut self, piece: &Piece) {
304 match piece {
305 Piece::Zero(bytes) => {
309 let _ = writeln!(self.out, "\t.space\t{bytes}");
310 }
311 Piece::Bytes(bytes) => {
312 let _ = writeln!(self.out, "\t.ascii\t\"{}\"", escape(bytes));
313 }
314 Piece::Scalar(bytes) => match width(bytes.len()) {
315 Some(directive) => {
316 let mut value = [0u8; 16];
317 value[..bytes.len()].copy_from_slice(bytes);
318 let _ = writeln!(self.out, "\t{directive}\t{}", u128::from_le_bytes(value));
319 }
320 None => {
323 let list = bytes.iter().map(u8::to_string).collect::<Vec<_>>().join(", ");
324 let _ = writeln!(self.out, "\t.byte\t{list}");
325 }
326 },
327 Piece::Away { symbol, addend } => {
332 let name = format!("{}{symbol}", self.directives.symbol());
333 match addend {
334 0 => {
335 let _ = writeln!(self.out, "\t.long\t{name} - .");
336 }
337 _ => {
338 let sign = if *addend < 0 { '-' } else { '+' };
339 let _ = writeln!(self.out, "\t.long\t{name}{sign}{} - .", addend.abs());
340 }
341 }
342 }
343 Piece::Addr { symbol, addend, bytes } => {
344 let directive = if *bytes == 8 { ".quad" } else { ".long" };
345 let name = format!("{}{symbol}", self.directives.symbol());
346 match addend {
347 0 => {
348 let _ = writeln!(self.out, "\t{directive}\t{name}");
349 }
350 _ => {
351 let sign = if *addend < 0 { '-' } else { '+' };
352 let _ = writeln!(self.out, "\t{directive}\t{name}{sign}{}", addend.abs());
353 }
354 }
355 }
356 Piece::Apart { to, from, addend, bytes } => {
359 let directive = width(usize::from(*bytes)).unwrap_or(".long");
360 let prefix = self.directives.symbol();
361 let _ = write!(self.out, "\t{directive}\t{prefix}{to}-{prefix}{from}");
362 let _ = match addend.signum() {
363 1 => writeln!(self.out, "+{addend}"),
364 -1 => writeln!(self.out, "-{}", addend.unsigned_abs()),
365 _ => writeln!(self.out),
366 };
367 }
368 }
369 }
370
371 fn number(&mut self, func: &Func) {
373 self.labels.clear();
374 self.labels.resize(func.block_count(), u32::MAX);
375 for (index, block) in func.blocks().enumerate() {
376 self.labels[block.index()] = u32::try_from(index).expect("a block number");
377 }
378 }
379
380 fn inst(
382 &mut self,
383 func: &Func,
384 block: Block,
385 inst: Inst,
386 func_name: &str,
387 ) -> Result<(), Error> {
388 if self.arch == Arch::Aarch64 {
389 let spelling = match self.directives {
390 Directives::MachO => aarch64::Spelling::Apple,
391 Directives::Elf | Directives::Coff => aarch64::Spelling::Gnu,
392 };
393 let at = a64::Context {
394 names: self.names,
395 symbol: self.directives.symbol(),
396 spelling,
397 func_name,
398 };
399 let mut line = String::new();
400 let label = |to| self.label(func_name, to);
401 let table = |at: u32| self.table(func_name, at as usize);
402 a64::inst(&mut line, &at, func, block, inst, label, table)?;
403 self.out.push_str(&line);
404 return Ok(());
405 }
406 let data = func[inst];
407 let spelled = self.names.resolve(data.opcode.name());
408 let opcode = spelled.strip_prefix(PREFIX).unwrap_or(spelled);
409 if opcode == x86_64::ALIGN {
415 let bytes = data.imm.map_or(0, |imm| func[imm].0);
416 let boundary = u32::try_from(bytes).ok().filter(|at| at.is_power_of_two());
417 let Some(boundary) = boundary else {
418 return Err(Error::Opcode {
419 func: func_name.to_owned(),
420 opcode: spelled.to_owned(),
421 });
422 };
423 let _ = writeln!(self.out, "\t.p2align\t{}, 0x90", boundary.trailing_zeros());
424 return Ok(());
425 }
426 if opcode == x86_64::LITERAL {
431 let bytes: Vec<u8> =
432 data.imm.map(|imm| x86_64::unpacked(func[imm].0).collect()).unwrap_or_default();
433 if bytes.is_empty() {
434 return Err(Error::Opcode {
435 func: func_name.to_owned(),
436 opcode: spelled.to_owned(),
437 });
438 }
439 let written: Vec<String> = bytes.iter().map(|byte| format!("0x{byte:02x}")).collect();
440 let _ = writeln!(self.out, "\t.byte\t{}", written.join(", "));
441 return Ok(());
442 }
443 if opcode == x86_64::TEMPLATE {
446 let Some(text) = data.symbol else {
447 return Err(Error::Opcode {
448 func: func_name.to_owned(),
449 opcode: spelled.to_owned(),
450 });
451 };
452 let mem = match data.mem {
453 Some(mem) => self.amode(&func[data.operands], &func[mem], func_name, spelled)?,
454 None => String::new(),
455 };
456 let operands = &func[data.operands];
460 if operands.iter().any(|operand| operand.reg.phys().is_none()) {
461 return Err(Error::Virtual {
462 func: func_name.to_owned(),
463 opcode: spelled.to_owned(),
464 });
465 }
466 let prefix = self.directives.symbol();
467 let reg = |at: usize, width: char| {
468 let Some(operand) = operands.get(at) else { return String::from("?") };
469 let phys = operand.reg.phys().expect("every operand was checked above");
470 let named = match width {
471 'b' => name_of(operand.class, phys, Width::Byte),
472 'w' => name_of(operand.class, phys, Width::Word),
473 'k' => name_of(operand.class, phys, Width::Long),
474 'h' => x86_64::gpr_high(phys).unwrap_or("?"),
475 _ => name_of(operand.class, phys, Width::Quad),
476 };
477 format!("%{named}")
478 };
479 let filled = x86_64::template_filled(
480 self.names.resolve(text),
481 &mem,
482 |name| format!("{prefix}{name}"),
483 reg,
484 );
485 for line in filled.lines() {
486 let _ = writeln!(self.out, "\t{}", line.trim_start());
487 }
488 return Ok(());
489 }
490 let Some(written) = x86_64::written(opcode) else {
491 return Err(Error::Opcode { func: func_name.to_owned(), opcode: spelled.to_owned() });
492 };
493 let operands = &func[data.operands];
494 for machine in written {
495 let mut args = Vec::with_capacity(machine.args.len());
496 for arg in machine.args {
497 args.push(match *arg {
498 Arg::Reg(at, width) => {
499 let operand = operands[usize::from(at)];
500 self.reg(operand, width, func_name, spelled)?
501 }
502 Arg::Xmm(at) => {
506 let operand = operands[usize::from(at)];
507 self.reg(operand, Width::Quad, func_name, spelled)?
508 }
509 Arg::Low(at) => {
514 let operand = operands[usize::from(at)];
515 self.reg(operand, Width::Byte, func_name, spelled)?
516 }
517 Arg::High(at) => {
518 let operand = operands[usize::from(at)];
519 let Some(phys) = operand.reg.phys() else {
520 return Err(Error::Virtual {
521 func: func_name.to_owned(),
522 opcode: spelled.to_owned(),
523 });
524 };
525 format!("%{}", x86_64::gpr_high(phys).unwrap_or("?"))
526 }
527 Arg::Named(register) => format!("%{register}"),
528 Arg::Stack(depth) => format!("%st({depth})"),
533 Arg::Lit(lane) => format!("${lane}"),
534 Arg::Through => {
537 let operand = operands[defs(operands)];
538 format!("*{}", self.reg(operand, Width::Quad, func_name, spelled)?)
539 }
540 Arg::Imm => match data.imm {
541 Some(imm) => format!("${}", func[imm].0),
542 None => "$0".to_owned(),
543 },
544 Arg::Mem => match data.mem {
545 Some(mem) => self.amode(operands, &func[mem], func_name, spelled)?,
546 None => "0".to_owned(),
547 },
548 Arg::Symbol => match data.symbol {
549 Some(symbol) => {
550 format!("{}{}", self.directives.symbol(), self.names.resolve(symbol))
551 }
552 None => "0".to_owned(),
553 },
554 Arg::Label => match func[block].succs.first() {
558 Some(call) => self.label(func_name, call.block),
559 None => "0".to_owned(),
560 },
561 });
562 }
563 if args.is_empty() {
564 let _ = writeln!(self.out, "\t{}", machine.mnemonic);
565 } else {
566 let _ = writeln!(self.out, "\t{}\t{}", machine.mnemonic, args.join(", "));
567 }
568 }
569 Ok(())
570 }
571
572 fn reg(
574 &self,
575 operand: Operand,
576 width: Width,
577 func_name: &str,
578 opcode: &str,
579 ) -> Result<String, Error> {
580 let Some(phys) = operand.reg.phys() else {
581 return Err(Error::Virtual { func: func_name.to_owned(), opcode: opcode.to_owned() });
582 };
583 Ok(format!("%{}", name_of(operand.class, phys, width)))
584 }
585
586 fn amode(
592 &self,
593 operands: &[Operand],
594 amode: &Amode,
595 func_name: &str,
596 opcode: &str,
597 ) -> Result<String, Error> {
598 let mut out = String::new();
599 match amode.segment {
602 Some(Segment::Fs) => out.push_str("%fs:"),
603 Some(Segment::Gs) => out.push_str("%gs:"),
604 None => {}
605 }
606 if let Some(symbol) = amode.symbol {
607 let _ = write!(out, "{}{}", self.directives.symbol(), self.names.resolve(symbol));
608 match amode.reach {
615 Reach::Itself => {}
616 Reach::Table => out.push_str("@GOTPCREL"),
617 Reach::Thread if self.directives == Directives::MachO => out.push_str("@TLVP"),
618 Reach::Thread => out.push_str("@GOTTPOFF"),
619 }
620 if amode.disp != 0 {
621 let sign = if amode.disp < 0 { '-' } else { '+' };
622 let _ = write!(out, "{sign}{}", i64::from(amode.disp).abs());
623 }
624 } else if let Some(block) = amode.block {
625 out.push_str(&self.label(func_name, block));
629 if amode.disp != 0 {
630 let sign = if amode.disp < 0 { '-' } else { '+' };
631 let _ = write!(out, "{sign}{}", i64::from(amode.disp).abs());
632 }
633 } else if let Some(table) = amode.table {
634 out.push_str(&self.table(func_name, table as usize));
636 if amode.disp != 0 {
637 let sign = if amode.disp < 0 { '-' } else { '+' };
638 let _ = write!(out, "{sign}{}", i64::from(amode.disp).abs());
639 }
640 } else if amode.disp != 0 || (amode.base.is_none() && amode.index.is_none()) {
641 let _ = write!(out, "{}", amode.disp);
644 }
645 let base = amode.base.and_then(|at| operands.get(usize::from(at)));
646 let index = amode.index.and_then(|at| operands.get(usize::from(at)));
647 if base.is_some() || index.is_some() {
648 out.push('(');
649 if let Some(operand) = base {
650 out.push_str(&self.reg(*operand, Width::Quad, func_name, opcode)?);
651 }
652 if let Some(operand) = index {
653 let reg = self.reg(*operand, Width::Quad, func_name, opcode)?;
654 let _ = write!(out, ",{reg},{}", amode.scale);
655 }
656 out.push(')');
657 } else if amode.symbol.is_some() || amode.block.is_some() || amode.table.is_some() {
658 out.push_str("(%rip)");
659 }
660 Ok(out)
661 }
662
663 fn tables(&mut self, func: &Func, func_name: &str) {
667 if func.tables.is_empty() {
668 return;
669 }
670 let _ = match self.fill() {
671 Some(byte) => writeln!(self.out, "\t.p2align\t2, {byte:#x}"),
672 None => writeln!(self.out, "\t.p2align\t2"),
673 };
674 for (index, table) in func.tables.iter().enumerate() {
675 let label = self.table(func_name, index);
676 let _ = writeln!(self.out, "{label}:");
677 let block = func.block_of(table.jump).expect("a table read by a jump in no block");
678 let succs = &func[block].succs;
679 for &cell in &table.cells {
680 let to = self.label(func_name, succs[cell as usize].block);
681 let _ = writeln!(self.out, "\t.long\t{to}-{label}");
682 }
683 }
684 }
685
686 fn table(&self, func_name: &str, index: usize) -> String {
689 format!("{}{func_name}_j{index}", self.directives.local())
690 }
691
692 fn prefix(&self) -> &'static str {
694 if self.arch == Arch::Aarch64 { a64::PREFIX } else { PREFIX }
695 }
696
697 fn fill(&self) -> Option<u8> {
700 if self.arch == Arch::Aarch64 { None } else { Some(0x90) }
701 }
702
703 fn label(&self, func_name: &str, block: Block) -> String {
705 match self.labels.get(block.index()).copied() {
706 Some(u32::MAX) | None => format!("{}{func_name}_?", self.directives.local()),
707 Some(number) => format!("{}{func_name}_{number}", self.directives.local()),
708 }
709 }
710}
711
712fn width(bytes: usize) -> Option<&'static str> {
714 match bytes {
715 1 => Some(".byte"),
716 2 => Some(".short"),
717 4 => Some(".long"),
718 8 => Some(".quad"),
719 _ => None,
720 }
721}
722
723fn escape(bytes: &[u8]) -> String {
729 let mut out = String::with_capacity(bytes.len());
730 for byte in bytes {
731 match byte {
732 b'"' => out.push_str("\\\""),
733 b'\\' => out.push_str("\\\\"),
734 0x20..=0x7e => out.push(char::from(*byte)),
735 _ => {
736 let _ = write!(out, "\\{byte:03o}");
737 }
738 }
739 }
740 out
741}
742
743fn name_of(class: RegClass, reg: PhysReg, width: Width) -> &'static str {
748 let named = if class == x86_64::GPR {
749 x86_64::gpr_name(reg, width)
750 } else {
751 x86_64::REGS.name(class, reg)
752 };
753 named.unwrap_or("?")
754}
755
756#[cfg(test)]
757mod tests {
758 use super::*;
759
760 use rucc_base::Interner;
761 use rucc_mir::{Func, Mem, Operand, Reg};
762 use rucc_object::{Binding, Place, Visibility};
763 use rucc_target::x86_64::{GPR, RAX, RCX, RDX, RSP};
764 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
765
766 fn target(os: Os) -> TargetInfo {
768 TargetInfo::new(Triple::new(Arch::X86_64, os, Env::Gnu))
769 }
770
771 fn write(build: impl FnOnce(&mut Func, &mut Interner)) -> String {
773 let mut names = Interner::new();
774 let mut func = Func::new(names.intern("f"));
775 build(&mut func, &mut names);
776 print(
777 &[func],
778 &Globals::default(),
779 &[],
780 &names,
781 &target(Os::Linux),
782 true,
783 Output::default(),
784 )
785 .expect("a function that was allocated")
786 }
787
788 fn data(vars: Vec<Variable>, os: Os) -> String {
790 let names = Interner::new();
791 print(
792 &[],
793 &Globals { vars, weak: Vec::new() },
794 &[],
795 &names,
796 &target(os),
797 true,
798 Output::default(),
799 )
800 .expect("a machine with a writer")
801 }
802
803 fn split(vars: Vec<Variable>, os: Os) -> String {
805 let names = Interner::new();
806 let sections =
807 Output { sections: Sections { functions: false, data: true }, ..Output::default() };
808 print(&[], &Globals { vars, weak: Vec::new() }, &[], &names, &target(os), true, sections)
809 .expect("a machine with a writer")
810 }
811
812 fn split_code(first: &str, second: &str, os: Os) -> String {
814 let mut names = Interner::new();
815 let mut funcs = Vec::new();
816 for name in [first, second] {
817 let mut func = Func::new(names.intern(name));
818 func.create_block();
819 funcs.push(func);
820 }
821 let sections =
822 Output { sections: Sections { functions: true, data: false }, ..Output::default() };
823 print(&funcs, &Globals::default(), &[], &names, &target(os), true, sections)
824 .expect("a machine with a writer")
825 }
826
827 fn var(name: &str, place: Place, pieces: Vec<Piece>) -> Variable {
829 Variable {
830 name: name.to_owned(),
831 size: 4,
832 align: 4,
833 place,
834 binding: Binding::Global,
835 visibility: Visibility::Default,
836 pieces,
837 }
838 }
839
840 fn body(text: &str) -> Vec<&str> {
842 text.lines()
843 .filter(|line| line.starts_with('\t') && !line.trim_start().starts_with('.'))
844 .map(|line| line.trim_start())
845 .collect()
846 }
847
848 #[test]
849 fn an_instruction_is_written_the_way_the_target_says_it_is() {
850 let text = write(|func, names| {
851 let block = func.create_block();
852 let add = Opcode::new(names.intern("x64.add_rr_32"));
853 func.build(block, add)
854 .operand(Operand::write(Reg::physical(RAX), GPR))
855 .operand(Operand::read(Reg::physical(RAX), GPR))
856 .operand(Operand::read(Reg::physical(RCX), GPR))
857 .finish();
858 });
859 assert_eq!(body(&text), ["addl\t%ecx, %eax"]);
862 }
863
864 #[test]
865 fn an_opcode_the_machine_has_no_single_instruction_for_is_written_as_the_ones_it_has() {
866 let text = write(|func, names| {
867 let block = func.create_block();
868 let cmp = Opcode::new(names.intern("x64.cmp_set_l_64"));
869 func.build(block, cmp)
870 .operand(Operand::write(Reg::physical(RAX), GPR))
871 .operand(Operand::read(Reg::physical(RCX), GPR))
872 .operand(Operand::read(Reg::physical(RDX), GPR))
873 .finish();
874 });
875 assert_eq!(body(&text), ["cmpq\t%rdx, %rcx", "setl\t%al"]);
878 }
879
880 #[test]
881 fn an_opcode_that_is_not_an_instruction_is_written_as_nothing() {
882 let text = write(|func, names| {
883 let block = func.create_block();
884 let ret = Opcode::new(names.intern("x64.ret_val_32"));
885 func.build(block, ret).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
886 });
887 assert_eq!(body(&text), Vec::<&str>::new());
888 }
889
890 #[test]
891 fn an_alignment_is_written_as_the_directive_that_asks_for_it() {
892 let text = write(|func, names| {
893 let block = func.create_block();
894 let align = Opcode::new(names.intern("x64.align"));
895 func.build(block, align).imm(32).finish();
896 });
897 assert!(text.contains("\n\t.p2align\t5, 0x90\n"), "{text}");
902 assert_eq!(body(&text), Vec::<&str>::new());
903 }
904
905 #[test]
906 fn an_address_is_a_displacement_and_then_the_registers_it_names() {
907 let text = write(|func, names| {
908 let block = func.create_block();
909 let lea = Opcode::new(names.intern("x64.lea_64"));
910 func.build(block, lea)
911 .operand(Operand::write(Reg::physical(RAX), GPR))
912 .mem(
913 Mem::at(Operand::read(Reg::physical(RCX), GPR))
914 .indexed(Operand::read(Reg::physical(RDX), GPR), 4)
915 .plus(-16),
916 )
917 .finish();
918 });
919 assert_eq!(body(&text), ["leaq\t-16(%rcx,%rdx,4), %rax"]);
920 }
921
922 #[test]
923 fn an_address_in_a_thread_s_own_block_names_the_segment_and_no_register() {
924 let text = write(|func, names| {
925 let block = func.create_block();
926 let load = Opcode::new(names.intern("x64.mov_rm_64"));
927 func.build(block, load)
928 .operand(Operand::write(Reg::physical(RAX), GPR))
929 .mem(Mem::in_segment(Segment::Fs, 40))
930 .finish();
931 });
932 assert_eq!(body(&text), ["movq\t%fs:40, %rax"]);
936 }
937
938 #[test]
939 fn the_touch_a_probing_prologue_writes_is_an_immediate_and_then_an_address() {
940 let text = write(|func, names| {
941 let block = func.create_block();
942 let touch = Opcode::new(names.intern("x64.or_mi_8"));
943 func.build(block, touch)
944 .imm(0)
945 .mem(Mem::at(Operand::read(Reg::physical(RSP), GPR)))
946 .finish();
947 });
948 assert_eq!(body(&text), ["orb\t$0, (%rsp)"]);
952 }
953
954 #[test]
955 fn an_address_with_nothing_but_a_symbol_in_it_is_relative_to_the_instruction_pointer() {
956 let text = write(|func, names| {
957 let block = func.create_block();
958 let load = Opcode::new(names.intern("x64.mov_rm_64"));
959 let global = names.intern("counter");
960 func.build(block, load)
961 .operand(Operand::write(Reg::physical(RAX), GPR))
962 .mem(Mem::of(global))
963 .finish();
964 });
965 assert_eq!(body(&text), ["movq\tcounter(%rip), %rax"]);
966 }
967
968 #[test]
969 fn an_address_with_a_label_in_it_is_the_label_and_is_relative_as_well() {
970 let text = write(|func, names| {
971 let head = func.create_block();
972 let there = func.create_block();
973 let lea = Opcode::new(names.intern("x64.lea_64"));
974 let jump = Opcode::new(names.intern("x64.jmp_reg"));
975 func.build(head, lea)
976 .operand(Operand::write(Reg::physical(RAX), GPR))
977 .mem(Mem::block(there))
978 .finish();
979 func.build(head, jump).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
980 func.build(there, Opcode::new(names.intern("x64.ret"))).finish();
981 });
982 assert_eq!(body(&text), ["leaq\t.Lf_1(%rip), %rax", "jmp\t*%rax", "ret"]);
986 }
987
988 #[test]
989 fn a_jump_table_is_a_label_and_the_distance_to_each_block_from_it() {
990 let text = write(|func, names| {
991 let head = func.create_block();
992 let first = func.create_block();
993 let second = func.create_block();
994 let lea = Opcode::new(names.intern("x64.lea_64"));
995 let jmp = Opcode::new(names.intern("x64.jmp_reg"));
996 func.build(head, lea)
997 .operand(Operand::write(Reg::physical(RAX), GPR))
998 .mem(Mem::table(0))
999 .finish();
1000 let jump =
1001 func.build(head, jmp).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
1002 func.succs_mut(head).push(rucc_mir::BlockCall::to(first));
1003 func.succs_mut(head).push(rucc_mir::BlockCall::to(second));
1004 func.build(first, Opcode::new(names.intern("x64.ret"))).finish();
1005 func.build(second, Opcode::new(names.intern("x64.ret"))).finish();
1006 func.tables.push(rucc_mir::Table { jump, cells: vec![0, 1, 0] });
1007 });
1008 assert_eq!(body(&text), ["leaq\t.Lf_j0(%rip), %rax", "jmp\t*%rax", "ret", "ret"]);
1009 let table: Vec<&str> = text.lines().skip_while(|line| *line != ".Lf_j0:").take(4).collect();
1010 assert_eq!(
1011 table,
1012 [".Lf_j0:", "\t.long\t.Lf_1-.Lf_j0", "\t.long\t.Lf_2-.Lf_j0", "\t.long\t.Lf_1-.Lf_j0"],
1013 "{text}"
1014 );
1015 }
1016
1017 #[test]
1018 fn an_address_that_reads_the_offset_table_says_so_on_the_symbol() {
1019 let text = write(|func, names| {
1020 let block = func.create_block();
1021 let load = Opcode::new(names.intern("x64.mov_rm_64"));
1022 let away = names.intern("away");
1023 func.build(block, load)
1024 .operand(Operand::write(Reg::physical(RAX), GPR))
1025 .mem(Mem::got(away))
1026 .finish();
1027 });
1028 assert_eq!(body(&text), ["movq\taway@GOTPCREL(%rip), %rax"]);
1032 }
1033
1034 #[test]
1035 fn an_address_that_reads_the_offset_of_a_thread_local_says_so_on_the_symbol_as_well() {
1036 let text = write(|func, names| {
1037 let block = func.create_block();
1038 let load = Opcode::new(names.intern("x64.mov_rm_64"));
1039 let away = names.intern("away");
1040 func.build(block, load)
1041 .operand(Operand::write(Reg::physical(RAX), GPR))
1042 .mem(Mem::thread(away))
1043 .finish();
1044 });
1045 assert_eq!(body(&text), ["movq\taway@GOTTPOFF(%rip), %rax"]);
1049 }
1050
1051 #[test]
1052 fn a_jump_goes_to_the_label_of_the_block_the_first_arm_names() {
1053 let mut names = Interner::new();
1054 let mut func = Func::new(names.intern("f"));
1055 let first = func.create_block();
1056 let second = func.create_block();
1057 let jmp = Opcode::new(names.intern("x64.jmp"));
1058 func.build(first, jmp).finish();
1059 func.succs_mut(first).push(rucc_mir::BlockCall::to(second));
1060 let text = print(
1061 &[func],
1062 &Globals::default(),
1063 &[],
1064 &names,
1065 &target(Os::Linux),
1066 true,
1067 Output::default(),
1068 )
1069 .expect("a function of two blocks");
1070 assert!(text.contains("\tjmp\t.Lf_1\n"), "{text}");
1071 assert!(text.contains("\n.Lf_1:\n"), "{text}");
1072 }
1073
1074 #[test]
1075 fn a_symbol_is_spelled_the_way_the_object_format_spells_one() {
1076 let mut names = Interner::new();
1077 let mut func = Func::new(names.intern("f"));
1078 let block = func.create_block();
1079 let call = Opcode::new(names.intern("x64.call"));
1080 let callee = names.intern("puts");
1081 func.build(block, call).symbol(callee).finish();
1082
1083 let elf = print(
1084 std::slice::from_ref(&func),
1085 &Globals::default(),
1086 &[],
1087 &names,
1088 &target(Os::Linux),
1089 true,
1090 Output::default(),
1091 )
1092 .expect("elf");
1093 assert!(elf.contains("\tcall\tputs\n"), "{elf}");
1094 assert!(elf.contains("\n.Lf_0:\n"), "{elf}");
1095
1096 let macho = print(
1099 &[func],
1100 &Globals::default(),
1101 &[],
1102 &names,
1103 &target(Os::Darwin),
1104 true,
1105 Output::default(),
1106 )
1107 .expect("mach-o");
1108 assert!(macho.contains("\tcall\t_puts\n"), "{macho}");
1109 assert!(macho.contains("\n_f:\n"), "{macho}");
1110 assert!(macho.contains("\nLf_0:\n"), "{macho}");
1111 }
1112
1113 #[test]
1114 fn a_function_that_was_never_allocated_is_refused_rather_than_written_wrongly() {
1115 let mut names = Interner::new();
1116 let mut func = Func::new(names.intern("f"));
1117 let block = func.create_block();
1118 let vreg = func.new_vreg(GPR);
1119 let neg = Opcode::new(names.intern("x64.neg_r_32"));
1120 func.build(block, neg).operand(Operand::write(vreg, GPR)).finish();
1121 let error = print(
1122 &[func],
1123 &Globals::default(),
1124 &[],
1125 &names,
1126 &target(Os::Linux),
1127 true,
1128 Output::default(),
1129 )
1130 .expect_err("a virtual register");
1131 assert_eq!(
1132 error,
1133 Error::Virtual { func: "f".to_owned(), opcode: "x64.neg_r_32".to_owned() }
1134 );
1135 }
1136
1137 #[test]
1138 fn an_opcode_the_target_does_not_describe_is_refused() {
1139 let mut names = Interner::new();
1140 let mut func = Func::new(names.intern("f"));
1141 let block = func.create_block();
1142 let made_up = Opcode::new(names.intern("x64.frobnicate"));
1143 func.build(block, made_up).finish();
1144 let error = print(
1145 &[func],
1146 &Globals::default(),
1147 &[],
1148 &names,
1149 &target(Os::Linux),
1150 true,
1151 Output::default(),
1152 )
1153 .expect_err("no such instruction");
1154 assert_eq!(
1155 error,
1156 Error::Opcode { func: "f".to_owned(), opcode: "x64.frobnicate".to_owned() }
1157 );
1158 }
1159
1160 #[test]
1161 fn a_function_no_other_file_can_see_is_not_announced_to_the_linker() {
1162 let mut names = Interner::new();
1163 let mut hidden = Func::new(names.intern("hidden"));
1164 hidden.binding = rucc_mir::Binding::Local;
1165 hidden.create_block();
1166 let text = print(
1167 &[hidden],
1168 &Globals::default(),
1169 &[],
1170 &names,
1171 &target(Os::Linux),
1172 true,
1173 Output::default(),
1174 )
1175 .expect("elf");
1176 assert!(text.contains("\nhidden:\n"), "{text}");
1179 assert!(text.contains("\t.type\thidden, @function\n"), "{text}");
1180 assert!(!text.contains(".globl"), "{text}");
1182 }
1183
1184 #[test]
1185 fn a_function_that_may_lose_to_another_definition_is_written_weak() {
1186 let mut names = Interner::new();
1187 let mut shared = Func::new(names.intern("shared"));
1188 shared.binding = rucc_mir::Binding::Weak;
1189 shared.create_block();
1190 let text = print(
1191 &[shared],
1192 &Globals::default(),
1193 &[],
1194 &names,
1195 &target(Os::Linux),
1196 true,
1197 Output::default(),
1198 )
1199 .expect("elf");
1200 assert!(text.contains("\t.weak\tshared\n"), "{text}");
1201 assert!(!text.contains(".globl"), "{text}");
1202 }
1203
1204 #[test]
1208 fn a_second_name_is_a_binding_and_a_set_and_nothing_else() {
1209 let names = Interner::new();
1210 let aliases = [
1211 Alias {
1212 name: "b".to_owned(),
1213 target: "a".to_owned(),
1214 binding: Binding::Global,
1215 visibility: Visibility::Default,
1216 },
1217 Alias {
1218 name: "c".to_owned(),
1219 target: "a".to_owned(),
1220 binding: Binding::Weak,
1221 visibility: Visibility::Default,
1222 },
1223 Alias {
1224 name: "d".to_owned(),
1225 target: "a".to_owned(),
1226 binding: Binding::Local,
1227 visibility: Visibility::Default,
1228 },
1229 ];
1230 let vars = vec![var("a", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])])];
1231 let text = print(
1232 &[],
1233 &Globals { vars, weak: Vec::new() },
1234 &aliases,
1235 &names,
1236 &target(Os::Linux),
1237 true,
1238 Output::default(),
1239 )
1240 .expect("a machine with a writer");
1241 assert!(text.contains("\t.globl\tb\n\t.set\tb,a\n"), "{text}");
1242 assert!(text.contains("\t.weak\tc\n\t.set\tc,a\n"), "{text}");
1243 assert!(text.contains("\t.set\td,a\n"), "{text}");
1246 assert!(!text.contains("\t.type\tb"), "the type comes from what it points at: {text}");
1247 assert!(!text.contains("\t.size\tb"), "and so does the size: {text}");
1248 assert_eq!(text.matches(".long\t1").count(), 1, "{text}");
1251 }
1252
1253 #[test]
1254 fn a_variable_is_a_section_a_name_and_the_bytes_between_them() {
1255 let text = data(
1256 vec![var("counter", Place::Written, vec![Piece::Scalar(vec![42, 0, 0, 0])])],
1257 Os::Linux,
1258 );
1259 assert!(text.contains("\t.data\n"), "{text}");
1260 assert!(text.contains("\t.globl\tcounter\n"), "{text}");
1261 assert!(text.contains("\t.p2align\t2\n"), "{text}");
1262 assert!(text.contains("\t.type\tcounter, @object\n"), "{text}");
1263 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
1266 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
1267 }
1268
1269 #[test]
1272 fn a_thread_local_variable_is_a_section_with_the_flag_on_it_and_a_type_of_its_own() {
1273 let text = data(
1274 vec![var(
1275 "counter",
1276 Place::Thread { zero: false },
1277 vec![Piece::Scalar(vec![42, 0, 0, 0])],
1278 )],
1279 Os::Linux,
1280 );
1281 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
1282 assert!(text.contains("\t.type\tcounter, @tls_object\n"), "{text}");
1283 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
1284 }
1285
1286 #[test]
1288 fn a_thread_local_variable_with_no_image_to_carry_goes_in_the_section_that_carries_none() {
1289 let text = data(
1290 vec![var("counter", Place::Thread { zero: true }, vec![Piece::Zero(4)])],
1291 Os::Linux,
1292 );
1293 assert!(text.contains("\t.section\t.tbss,\"awT\",@nobits\n"), "{text}");
1294 assert!(text.contains("\t.type\tcounter, @tls_object\n"), "{text}");
1295 assert!(text.contains("\ncounter:\n\t.space\t4\n"), "{text}");
1296 }
1297
1298 #[test]
1301 fn a_thread_local_variable_on_mach_o_is_an_image_and_a_descriptor() {
1302 let text = data(
1303 vec![var(
1304 "counter",
1305 Place::Thread { zero: false },
1306 vec![Piece::Scalar(vec![42, 0, 0, 0])],
1307 )],
1308 Os::Darwin,
1309 );
1310 let image = "\t.section\t__DATA,__thread_data,thread_local_regular\n\t.p2align\t2\n\
1311 _counter$tlv$init:\n\t.long\t42\n";
1312 assert!(text.contains(image), "{text}");
1313 let descriptor = "\t.section\t__DATA,__thread_vars,thread_local_variables\n\
1314 \t.globl\t_counter\n\t.p2align\t3\n_counter:\n\
1315 \t.quad\t__tlv_bootstrap\n\t.quad\t0\n\t.quad\t_counter$tlv$init\n";
1316 assert!(text.contains(descriptor), "{text}");
1317 let zero = data(
1318 vec![var("counter", Place::Thread { zero: true }, vec![Piece::Zero(4)])],
1319 Os::Darwin,
1320 );
1321 assert!(zero.contains("\t.tbss\t_counter$tlv$init,4,2\n"), "{zero}");
1322 assert!(zero.contains(descriptor), "{zero}");
1323 }
1324
1325 #[test]
1326 fn a_variable_no_other_file_can_see_is_not_announced_to_the_linker() {
1327 let mut hidden = var("hidden", Place::Zero, vec![Piece::Zero(4)]);
1328 hidden.binding = Binding::Local;
1329 let text = data(vec![hidden], Os::Linux);
1330 assert!(text.contains("\t.bss\n"), "{text}");
1331 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
1332 assert!(!text.contains(".globl"), "{text}");
1335 }
1336
1337 #[test]
1338 fn a_tentative_definition_is_a_request_rather_than_a_section_and_a_label() {
1339 let text = data(vec![var("x", Place::Merged, vec![Piece::Zero(4)])], Os::Linux);
1340 assert_eq!(text.lines().find(|line| line.contains(".comm")), Some("\t.comm\tx,4,4"));
1341 assert!(!text.contains("\nx:\n"), "nothing here says where it is: {text}");
1342 }
1343
1344 #[test]
1351 fn every_function_gets_a_section_of_its_own_when_that_is_what_was_asked_for() {
1352 let text = split_code("first", "second", Os::Linux);
1353 assert!(text.starts_with("\t.text\n"), "{text}");
1354 assert!(text.contains("\t.section\t.text.first,\"ax\",@progbits\n"), "{text}");
1355 assert!(text.contains("\t.section\t.text.second,\"ax\",@progbits\n"), "{text}");
1356 let opened = text.find(".section\t.text.first").expect("a section");
1359 assert!(opened < text.find("\nfirst:\n").expect("a label"), "{text}");
1360 let plain = write(|_, _| {});
1362 assert!(!plain.contains(".text."), "{plain}");
1363 }
1364
1365 #[test]
1369 fn a_format_that_already_lets_the_linker_split_a_section_is_not_asked_to_split_it_again() {
1370 let text = split_code("first", "second", Os::Darwin);
1371 assert!(text.contains("\t.subsections_via_symbols\n"), "{text}");
1372 assert_eq!(text.matches(".section").count(), 1, "the one it opens with: {text}");
1373 let vars = vec![var("counter", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])])];
1374 assert_eq!(split(vars.clone(), Os::Darwin), data(vars, Os::Darwin));
1375 }
1376
1377 #[test]
1381 fn every_variable_gets_a_section_named_after_it_when_that_is_what_was_asked_for() {
1382 let vars = vec![
1383 var("g", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])]),
1384 var("z", Place::Zero, vec![Piece::Zero(4)]),
1385 var("r", Place::ReadOnly, vec![Piece::Scalar(vec![3, 0, 0, 0])]),
1386 ];
1387 let text = split(vars.clone(), Os::Linux);
1388 assert!(text.contains("\t.section\t.data.g,\"aw\"\n\t.globl\tg\n"), "{text}");
1389 assert!(text.contains("\t.section\t.bss.z,\"aw\",@nobits\n"), "{text}");
1390 assert!(text.contains("\t.section\t.rodata.r,\"a\"\n"), "{text}");
1391 assert!(text.contains("\ng:\n\t.long\t1\n"), "{text}");
1394 assert!(text.contains("\t.size\tg, .-g\n"), "{text}");
1395 assert!(text.contains("\t.space\t4\n"), "{text}");
1396 assert!(!text.contains(".text."), "{text}");
1399 let plain = data(vars, Os::Linux);
1400 assert!(plain.contains("\t.data\n") && plain.contains("\t.bss\n"), "{plain}");
1401 assert!(!plain.contains(".data.g"), "{plain}");
1402 }
1403
1404 #[test]
1405 fn the_object_format_decides_how_a_variable_is_written_as_much_as_a_function() {
1406 let text = data(vec![var("x", Place::Zero, vec![Piece::Zero(4)])], Os::Darwin);
1407 assert!(text.contains("\t.globl\t_x\n\t.zerofill\t__DATA,__bss,_x,4,2\n"), "{text}");
1412 let read_only = data(vec![var("x", Place::ReadOnly, vec![Piece::Zero(4)])], Os::Darwin);
1413 assert!(read_only.contains("\t.section\t__TEXT,__const\n"), "{read_only}");
1414 assert!(read_only.contains("\n_x:\n"), "the underscore, without which nothing links");
1415 }
1416
1417 #[test]
1418 fn a_run_of_bytes_is_written_so_that_it_reads_back_as_the_same_bytes() {
1419 let bytes = Piece::Bytes(b"a\"b\\\n\0\x801".to_vec());
1420 let text = data(vec![var("s", Place::ReadOnly, vec![bytes])], Os::Linux);
1421 assert!(text.contains("\t.ascii\t\"a\\\"b\\\\\\012\\000\\2001\"\n"), "{text}");
1424 }
1425
1426 #[test]
1427 fn the_address_of_a_name_in_an_image_is_written_as_the_name() {
1428 let addr = Piece::Addr { symbol: "y".to_owned(), addend: 16, bytes: 8 };
1429 let text = data(vec![var("p", Place::Written, vec![addr])], Os::Linux);
1430 assert!(text.contains("\np:\n\t.quad\ty+16\n"), "{text}");
1431 }
1432
1433 #[test]
1434 fn a_distance_in_an_image_is_written_as_the_name_less_where_it_is() {
1435 let away = Piece::Away { symbol: "y".to_owned(), addend: 0 };
1436 let text = data(vec![var("d", Place::ReadOnly, vec![away])], Os::Linux);
1437 assert!(text.contains("\nd:\n\t.long\ty - .\n"), "{text}");
1438
1439 let away = Piece::Away { symbol: "y".to_owned(), addend: -3 };
1440 let text = data(vec![var("d", Place::ReadOnly, vec![away])], Os::Linux);
1441 assert!(text.contains("\nd:\n\t.long\ty-3 - .\n"), "{text}");
1442 }
1443
1444 #[test]
1445 fn a_distance_between_two_labels_is_written_as_one_less_the_other() {
1446 let piece = |addend, bytes| Piece::Apart {
1447 to: ".Llbl.1".to_owned(),
1448 from: ".Llbl.0".to_owned(),
1449 addend,
1450 bytes,
1451 };
1452 let text = data(vec![var("b", Place::ReadOnly, vec![piece(0, 4)])], Os::Linux);
1453 assert!(text.contains("\nb:\n\t.long\t.Llbl.1-.Llbl.0\n"), "{text}");
1454
1455 let text = data(vec![var("b", Place::ReadOnly, vec![piece(-2, 2)])], Os::Linux);
1456 assert!(text.contains("\nb:\n\t.short\t.Llbl.1-.Llbl.0-2\n"), "{text}");
1457 }
1458
1459 #[test]
1460 fn a_machine_with_no_writer_here_is_said_so_rather_than_written_as_x86_64() {
1461 let names = Interner::new();
1462 let riscv = TargetInfo::new(Triple::new(Arch::Riscv64, Os::Linux, Env::Gnu));
1463 let error = print(&[], &Globals::default(), &[], &names, &riscv, true, Output::default())
1464 .expect_err("no writer");
1465 assert!(matches!(error, Error::Machine { .. }), "{error:?}");
1466 }
1467
1468 fn write_a64(build: impl FnOnce(&mut Func, &mut Interner)) -> Result<String, Error> {
1470 let mut names = Interner::new();
1471 let mut func = Func::new(names.intern("f"));
1472 build(&mut func, &mut names);
1473 let target = TargetInfo::new(Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu));
1474 print(&[func], &Globals::default(), &[], &names, &target, true, Output::default())
1475 }
1476
1477 #[test]
1478 fn an_aarch64_instruction_is_written_the_way_its_own_table_says() {
1479 use rucc_target::aarch64::{self, x};
1480 let text = write_a64(|func, names| {
1481 let block = func.create_block();
1482 let add = Opcode::new(names.intern("a64.add_rr_32"));
1483 func.build(block, add)
1484 .operand(Operand::write(Reg::physical(x(0)), aarch64::GPR))
1485 .operand(Operand::read(Reg::physical(x(1)), aarch64::GPR))
1486 .operand(Operand::read(Reg::physical(x(2)), aarch64::GPR))
1487 .finish();
1488 let load = Opcode::new(names.intern("a64.ldr_64"));
1489 let base = Operand::read(Reg::physical(aarch64::SP), aarch64::GPR);
1490 func.build(block, load)
1491 .operand(Operand::write(Reg::physical(x(3)), aarch64::GPR))
1492 .mem(Mem::at(base).plus(16))
1493 .finish();
1494 })
1495 .expect("an allocated function");
1496 assert_eq!(body(&text), ["add w0, w1, w2", "ldr x3, [sp, #16]"]);
1499 assert!(text.contains("\t.p2align\t4\n"), "{text}");
1501 assert!(!text.contains("0x90"), "{text}");
1502 }
1503
1504 #[test]
1505 fn an_aarch64_register_left_virtual_is_refused() {
1506 let error = write_a64(|func, names| {
1507 let block = func.create_block();
1508 let mov = Opcode::new(names.intern("a64.mov_rr_64"));
1509 let class = aarch64::GPR;
1510 let v0 = func.new_vreg(class);
1511 let v1 = func.new_vreg(class);
1512 func.build(block, mov)
1513 .operand(Operand::write(v0, class))
1514 .operand(Operand::read(v1, class))
1515 .finish();
1516 })
1517 .expect_err("a register was never allocated");
1518 assert!(matches!(error, Error::Virtual { .. }), "{error:?}");
1519 }
1520
1521 #[test]
1522 fn an_aarch64_access_through_an_index_shifts_it_by_the_size() {
1523 use rucc_target::aarch64::{self, x};
1524 let text = write_a64(|func, names| {
1525 let block = func.create_block();
1526 let store = Opcode::new(names.intern("a64.str_32"));
1527 let base = Operand::read(Reg::physical(x(0)), aarch64::GPR);
1528 let index = Operand::read(Reg::physical(x(2)), aarch64::GPR);
1529 func.build(block, store)
1530 .operand(Operand::read(Reg::physical(x(3)), aarch64::GPR))
1531 .mem(Mem::at(base).indexed(index, 4))
1532 .finish();
1533 let load = Opcode::new(names.intern("a64.ldr_64"));
1534 func.build(block, load)
1535 .operand(Operand::write(Reg::physical(x(1)), aarch64::GPR))
1536 .mem(Mem::at(base).indexed(index, 1))
1537 .finish();
1538 })
1539 .expect("an allocated function");
1540 assert_eq!(body(&text), ["str w3, [x0, x2, lsl #2]", "ldr x1, [x0, x2]"]);
1541 }
1542
1543 #[test]
1544 fn an_aarch64_offset_the_encoder_refuses_is_not_written() {
1545 use rucc_target::aarch64::{self, x};
1546 let error = write_a64(|func, names| {
1547 let block = func.create_block();
1548 let load = Opcode::new(names.intern("a64.ldr_64"));
1549 let base = Operand::read(Reg::physical(x(0)), aarch64::GPR);
1550 func.build(block, load)
1551 .operand(Operand::write(Reg::physical(x(1)), aarch64::GPR))
1552 .mem(Mem::at(base).plus(1 << 20))
1553 .finish();
1554 })
1555 .expect_err("an offset no load can hold");
1556 assert!(matches!(error, Error::Encode { .. }), "{error:?}");
1557 }
1558
1559 #[test]
1560 fn an_opcode_aarch64_does_not_have_is_refused_rather_than_written_as_x86() {
1561 let error = write_a64(|func, names| {
1562 let block = func.create_block();
1563 func.build(block, Opcode::new(names.intern("x64.ret"))).finish();
1564 })
1565 .expect_err("not an AArch64 opcode");
1566 assert!(matches!(error, Error::Opcode { .. }), "{error:?}");
1567 }
1568
1569 #[test]
1570 fn the_head_of_a_loop_is_asked_to_stay_inside_one_line() {
1571 let mut names = Interner::new();
1572 let mut func = Func::new(names.intern("f"));
1573 func.create_block();
1574 let head = func.create_block();
1575 let jmp = Opcode::new(names.intern("x64.jmp"));
1576 func.build(head, jmp).finish();
1577 func.succs_mut(head).push(rucc_mir::BlockCall::to(head));
1578 func.heads = vec![head];
1579 let text = print(
1580 &[func],
1581 &Globals::default(),
1582 &[],
1583 &names,
1584 &target(Os::Linux),
1585 true,
1586 Output::default(),
1587 )
1588 .expect("a function with a loop in it");
1589 let wanted = "\n.Lf_0:\n\t.p2align\t6,,4\n.Lf_1:\n";
1592 assert!(text.contains(wanted), "{text}");
1593 }
1594}