1use std::fmt::Write as _;
44
45use rucc_base::Interner;
46use rucc_mir::{Amode, Block, CfiOp, Func, Inst, Opcode, Operand, Reach, defs};
47use rucc_object::{Alias, FUNC_ALIGN, Output, Sections};
48use rucc_target::x86_64::{self, Arg, Width};
49use rucc_target::{PhysReg, RegClass, Segment, TargetInfo};
50use rucc_tuple::Arch;
51
52use crate::Error;
53use crate::data::{Globals, Piece, Variable};
54use crate::format::{Directives, binding, visibility};
55
56const PREFIX: &str = "x64.";
62
63pub fn print(
82 funcs: &[Func],
83 globals: &Globals,
84 aliases: &[Alias],
85 names: &Interner,
86 target: &TargetInfo,
87 unwind: bool,
88 output: Output,
89) -> Result<String, Error> {
90 let Output { sections, property } = output;
91 if target.tuple.arch() != Arch::X86_64 {
92 return Err(Error::Machine { triple: target.tuple.to_string() });
93 }
94 let directives = Directives::of(target.object_format);
95 let mut writer = Writer {
96 names,
97 directives,
98 unwind: unwind && directives == Directives::Elf,
101 out: String::new(),
102 labels: Vec::new(),
103 sections,
104 };
105 writer.out.push_str(writer.directives.text());
106 writer.out.push('\n');
107 for func in funcs {
108 writer.func(func)?;
109 }
110 for var in &globals.vars {
111 writer.variable(var);
112 }
113 for alias in aliases {
114 writer.directives.alias(&mut writer.out, alias);
115 }
116 for name in &globals.weak {
119 writer.directives.absent(&mut writer.out, name);
120 }
121 writer.directives.end(&mut writer.out, property);
122 Ok(writer.out)
123}
124
125struct Writer<'a> {
127 names: &'a Interner,
128 directives: Directives,
129 unwind: bool,
131 out: String,
132 labels: Vec<u32>,
135 sections: Sections,
137}
138
139impl Writer<'_> {
140 fn func(&mut self, func: &Func) -> Result<(), Error> {
142 let name = self.names.resolve(func.name).to_owned();
143 self.number(func);
144 let binding = binding(func.binding);
145 let seen = visibility(func.visibility);
146 let align = func.align.unwrap_or(FUNC_ALIGN);
147 self.directives.code(&mut self.out, &name, self.sections);
148 let patch =
152 func.patch.map(|patch| (patch, format!("{}pfe_{name}", self.directives.local())));
153 let mut ahead = String::new();
154 if let Some((patch, label)) = &patch {
155 let back = if self.sections.functions {
156 format!("\t.section\t.text.{name}")
157 } else {
158 self.directives.text().to_owned()
159 };
160 self.directives.patchable(&mut ahead, label, &back);
161 if patch.before > 0 {
162 let _ = writeln!(ahead, "{label}:");
163 self.pad(&mut ahead, patch.pad, patch.before);
164 }
165 }
166 self.directives.open(&mut self.out, &name, align, binding, seen, &ahead);
167 let unwind = self.unwind;
168 if unwind {
169 let _ = writeln!(self.out, "\t.cfi_startproc");
170 }
171 let end = func.cfi_end();
172 for (index, block) in func.blocks().enumerate() {
173 let _ = writeln!(self.out, "{}{name}_{index}:", self.directives.local());
174 if let Some(label) = func.block_name(block) {
179 let _ = writeln!(self.out, "{}:", self.names.resolve(label));
180 }
181 for inst in func.insts(block) {
182 if let Some((patch, label)) = &patch {
188 if patch.before == 0 && patch.after == Some(inst) {
189 let _ = writeln!(self.out, "{label}:");
190 }
191 }
192 self.inst(func, block, inst, &name)?;
193 if unwind && Some(inst) != end {
194 for op in func.cfi_after(inst) {
195 self.cfi(op);
196 }
197 }
198 }
199 }
200 if unwind {
201 let _ = writeln!(self.out, "\t.cfi_endproc");
202 }
203 self.directives.close(&mut self.out, &name);
204 Ok(())
205 }
206
207 fn pad(&self, out: &mut String, pad: Opcode, count: u32) {
214 let spelled = self.names.resolve(pad.name());
215 let opcode = spelled.strip_prefix(PREFIX).unwrap_or(spelled);
216 for _ in 0..count {
217 let _ = writeln!(out, "\t{opcode}");
218 }
219 }
220
221 fn cfi(&mut self, op: CfiOp) {
228 let _ = match op {
229 CfiOp::DefCfa { reg, offset } => {
230 writeln!(self.out, "\t.cfi_def_cfa {reg}, {offset}")
231 }
232 CfiOp::DefCfaOffset(offset) => writeln!(self.out, "\t.cfi_def_cfa_offset {offset}"),
233 CfiOp::DefCfaRegister(reg) => writeln!(self.out, "\t.cfi_def_cfa_register {reg}"),
234 CfiOp::Offset { reg, offset } => writeln!(self.out, "\t.cfi_offset {reg}, {offset}"),
235 CfiOp::Restore(reg) => writeln!(self.out, "\t.cfi_restore {reg}"),
236 CfiOp::RememberState => writeln!(self.out, "\t.cfi_remember_state"),
237 CfiOp::RestoreState => writeln!(self.out, "\t.cfi_restore_state"),
238 };
239 }
240
241 fn variable(&mut self, var: &Variable) {
243 if !self.directives.variable(&mut self.out, var, self.sections) {
244 return;
245 }
246 for piece in &var.pieces {
247 self.piece(piece);
248 }
249 self.directives.close(&mut self.out, &var.name);
250 }
251
252 fn piece(&mut self, piece: &Piece) {
258 match piece {
259 Piece::Zero(bytes) => {
263 let _ = writeln!(self.out, "\t.space\t{bytes}");
264 }
265 Piece::Bytes(bytes) => {
266 let _ = writeln!(self.out, "\t.ascii\t\"{}\"", escape(bytes));
267 }
268 Piece::Scalar(bytes) => match width(bytes.len()) {
269 Some(directive) => {
270 let mut value = [0u8; 16];
271 value[..bytes.len()].copy_from_slice(bytes);
272 let _ = writeln!(self.out, "\t{directive}\t{}", u128::from_le_bytes(value));
273 }
274 None => {
277 let list = bytes.iter().map(u8::to_string).collect::<Vec<_>>().join(", ");
278 let _ = writeln!(self.out, "\t.byte\t{list}");
279 }
280 },
281 Piece::Away { symbol, addend } => {
286 let name = format!("{}{symbol}", self.directives.symbol());
287 match addend {
288 0 => {
289 let _ = writeln!(self.out, "\t.long\t{name} - .");
290 }
291 _ => {
292 let sign = if *addend < 0 { '-' } else { '+' };
293 let _ = writeln!(self.out, "\t.long\t{name}{sign}{} - .", addend.abs());
294 }
295 }
296 }
297 Piece::Addr { symbol, addend, bytes } => {
298 let directive = if *bytes == 8 { ".quad" } else { ".long" };
299 let name = format!("{}{symbol}", self.directives.symbol());
300 match addend {
301 0 => {
302 let _ = writeln!(self.out, "\t{directive}\t{name}");
303 }
304 _ => {
305 let sign = if *addend < 0 { '-' } else { '+' };
306 let _ = writeln!(self.out, "\t{directive}\t{name}{sign}{}", addend.abs());
307 }
308 }
309 }
310 }
311 }
312
313 fn number(&mut self, func: &Func) {
315 self.labels.clear();
316 self.labels.resize(func.block_count(), u32::MAX);
317 for (index, block) in func.blocks().enumerate() {
318 self.labels[block.index()] = u32::try_from(index).expect("a block number");
319 }
320 }
321
322 fn inst(
324 &mut self,
325 func: &Func,
326 block: Block,
327 inst: Inst,
328 func_name: &str,
329 ) -> Result<(), Error> {
330 let data = func[inst];
331 let spelled = self.names.resolve(data.opcode.name());
332 let opcode = spelled.strip_prefix(PREFIX).unwrap_or(spelled);
333 if opcode == x86_64::ALIGN {
339 let bytes = data.imm.map_or(0, |imm| func[imm].0);
340 let boundary = u32::try_from(bytes).ok().filter(|at| at.is_power_of_two());
341 let Some(boundary) = boundary else {
342 return Err(Error::Opcode {
343 func: func_name.to_owned(),
344 opcode: spelled.to_owned(),
345 });
346 };
347 let _ = writeln!(self.out, "\t.p2align\t{}, 0x90", boundary.trailing_zeros());
348 return Ok(());
349 }
350 if opcode == x86_64::LITERAL {
355 let bytes: Vec<u8> =
356 data.imm.map(|imm| x86_64::unpacked(func[imm].0).collect()).unwrap_or_default();
357 if bytes.is_empty() {
358 return Err(Error::Opcode {
359 func: func_name.to_owned(),
360 opcode: spelled.to_owned(),
361 });
362 }
363 let written: Vec<String> = bytes.iter().map(|byte| format!("0x{byte:02x}")).collect();
364 let _ = writeln!(self.out, "\t.byte\t{}", written.join(", "));
365 return Ok(());
366 }
367 let Some(written) = x86_64::written(opcode) else {
368 return Err(Error::Opcode { func: func_name.to_owned(), opcode: spelled.to_owned() });
369 };
370 let operands = &func[data.operands];
371 for machine in written {
372 let mut args = Vec::with_capacity(machine.args.len());
373 for arg in machine.args {
374 args.push(match *arg {
375 Arg::Reg(at, width) => {
376 let operand = operands[usize::from(at)];
377 self.reg(operand, width, func_name, spelled)?
378 }
379 Arg::Xmm(at) => {
383 let operand = operands[usize::from(at)];
384 self.reg(operand, Width::Quad, func_name, spelled)?
385 }
386 Arg::Low(at) => {
391 let operand = operands[usize::from(at)];
392 self.reg(operand, Width::Byte, func_name, spelled)?
393 }
394 Arg::High(at) => {
395 let operand = operands[usize::from(at)];
396 let Some(phys) = operand.reg.phys() else {
397 return Err(Error::Virtual {
398 func: func_name.to_owned(),
399 opcode: spelled.to_owned(),
400 });
401 };
402 format!("%{}", x86_64::gpr_high(phys).unwrap_or("?"))
403 }
404 Arg::Named(register) => format!("%{register}"),
405 Arg::Stack(depth) => format!("%st({depth})"),
410 Arg::Lit(lane) => format!("${lane}"),
411 Arg::Through => {
414 let operand = operands[defs(operands)];
415 format!("*{}", self.reg(operand, Width::Quad, func_name, spelled)?)
416 }
417 Arg::Imm => match data.imm {
418 Some(imm) => format!("${}", func[imm].0),
419 None => "$0".to_owned(),
420 },
421 Arg::Mem => match data.mem {
422 Some(mem) => self.amode(operands, &func[mem], func_name, spelled)?,
423 None => "0".to_owned(),
424 },
425 Arg::Symbol => match data.symbol {
426 Some(symbol) => {
427 format!("{}{}", self.directives.symbol(), self.names.resolve(symbol))
428 }
429 None => "0".to_owned(),
430 },
431 Arg::Label => match func[block].succs.first() {
435 Some(call) => self.label(func_name, call.block),
436 None => "0".to_owned(),
437 },
438 });
439 }
440 if args.is_empty() {
441 let _ = writeln!(self.out, "\t{}", machine.mnemonic);
442 } else {
443 let _ = writeln!(self.out, "\t{}\t{}", machine.mnemonic, args.join(", "));
444 }
445 }
446 Ok(())
447 }
448
449 fn reg(
451 &self,
452 operand: Operand,
453 width: Width,
454 func_name: &str,
455 opcode: &str,
456 ) -> Result<String, Error> {
457 let Some(phys) = operand.reg.phys() else {
458 return Err(Error::Virtual { func: func_name.to_owned(), opcode: opcode.to_owned() });
459 };
460 Ok(format!("%{}", name_of(operand.class, phys, width)))
461 }
462
463 fn amode(
469 &self,
470 operands: &[Operand],
471 amode: &Amode,
472 func_name: &str,
473 opcode: &str,
474 ) -> Result<String, Error> {
475 let mut out = String::new();
476 match amode.segment {
479 Some(Segment::Fs) => out.push_str("%fs:"),
480 Some(Segment::Gs) => out.push_str("%gs:"),
481 None => {}
482 }
483 if let Some(symbol) = amode.symbol {
484 let _ = write!(out, "{}{}", self.directives.symbol(), self.names.resolve(symbol));
485 match amode.reach {
490 Reach::Itself => {}
491 Reach::Table => out.push_str("@GOTPCREL"),
492 Reach::Thread => out.push_str("@GOTTPOFF"),
493 }
494 if amode.disp != 0 {
495 let sign = if amode.disp < 0 { '-' } else { '+' };
496 let _ = write!(out, "{sign}{}", i64::from(amode.disp).abs());
497 }
498 } else if let Some(block) = amode.block {
499 out.push_str(&self.label(func_name, block));
503 if amode.disp != 0 {
504 let sign = if amode.disp < 0 { '-' } else { '+' };
505 let _ = write!(out, "{sign}{}", i64::from(amode.disp).abs());
506 }
507 } else if amode.disp != 0 || (amode.base.is_none() && amode.index.is_none()) {
508 let _ = write!(out, "{}", amode.disp);
511 }
512 let base = amode.base.and_then(|at| operands.get(usize::from(at)));
513 let index = amode.index.and_then(|at| operands.get(usize::from(at)));
514 if base.is_some() || index.is_some() {
515 out.push('(');
516 if let Some(operand) = base {
517 out.push_str(&self.reg(*operand, Width::Quad, func_name, opcode)?);
518 }
519 if let Some(operand) = index {
520 let reg = self.reg(*operand, Width::Quad, func_name, opcode)?;
521 let _ = write!(out, ",{reg},{}", amode.scale);
522 }
523 out.push(')');
524 } else if amode.symbol.is_some() || amode.block.is_some() {
525 out.push_str("(%rip)");
526 }
527 Ok(out)
528 }
529
530 fn label(&self, func_name: &str, block: Block) -> String {
532 match self.labels.get(block.index()).copied() {
533 Some(u32::MAX) | None => format!("{}{func_name}_?", self.directives.local()),
534 Some(number) => format!("{}{func_name}_{number}", self.directives.local()),
535 }
536 }
537}
538
539fn width(bytes: usize) -> Option<&'static str> {
541 match bytes {
542 1 => Some(".byte"),
543 2 => Some(".short"),
544 4 => Some(".long"),
545 8 => Some(".quad"),
546 _ => None,
547 }
548}
549
550fn escape(bytes: &[u8]) -> String {
556 let mut out = String::with_capacity(bytes.len());
557 for byte in bytes {
558 match byte {
559 b'"' => out.push_str("\\\""),
560 b'\\' => out.push_str("\\\\"),
561 0x20..=0x7e => out.push(char::from(*byte)),
562 _ => {
563 let _ = write!(out, "\\{byte:03o}");
564 }
565 }
566 }
567 out
568}
569
570fn name_of(class: RegClass, reg: PhysReg, width: Width) -> &'static str {
575 let named = if class == x86_64::GPR {
576 x86_64::gpr_name(reg, width)
577 } else {
578 x86_64::REGS.name(class, reg)
579 };
580 named.unwrap_or("?")
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586
587 use rucc_base::Interner;
588 use rucc_mir::{Func, Mem, Operand, Reg};
589 use rucc_object::{Binding, Place, Visibility};
590 use rucc_target::x86_64::{GPR, RAX, RCX, RDX, RSP};
591 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
592
593 fn target(os: Os) -> TargetInfo {
595 TargetInfo::new(Triple::new(Arch::X86_64, os, Env::Gnu))
596 }
597
598 fn write(build: impl FnOnce(&mut Func, &mut Interner)) -> String {
600 let mut names = Interner::new();
601 let mut func = Func::new(names.intern("f"));
602 build(&mut func, &mut names);
603 print(
604 &[func],
605 &Globals::default(),
606 &[],
607 &names,
608 &target(Os::Linux),
609 true,
610 Output::default(),
611 )
612 .expect("a function that was allocated")
613 }
614
615 fn data(vars: Vec<Variable>, os: Os) -> String {
617 let names = Interner::new();
618 print(
619 &[],
620 &Globals { vars, weak: Vec::new() },
621 &[],
622 &names,
623 &target(os),
624 true,
625 Output::default(),
626 )
627 .expect("a machine with a writer")
628 }
629
630 fn split(vars: Vec<Variable>, os: Os) -> String {
632 let names = Interner::new();
633 let sections =
634 Output { sections: Sections { functions: false, data: true }, ..Output::default() };
635 print(&[], &Globals { vars, weak: Vec::new() }, &[], &names, &target(os), true, sections)
636 .expect("a machine with a writer")
637 }
638
639 fn split_code(first: &str, second: &str, os: Os) -> String {
641 let mut names = Interner::new();
642 let mut funcs = Vec::new();
643 for name in [first, second] {
644 let mut func = Func::new(names.intern(name));
645 func.create_block();
646 funcs.push(func);
647 }
648 let sections =
649 Output { sections: Sections { functions: true, data: false }, ..Output::default() };
650 print(&funcs, &Globals::default(), &[], &names, &target(os), true, sections)
651 .expect("a machine with a writer")
652 }
653
654 fn var(name: &str, place: Place, pieces: Vec<Piece>) -> Variable {
656 Variable {
657 name: name.to_owned(),
658 size: 4,
659 align: 4,
660 place,
661 binding: Binding::Global,
662 visibility: Visibility::Default,
663 pieces,
664 }
665 }
666
667 fn body(text: &str) -> Vec<&str> {
669 text.lines()
670 .filter(|line| line.starts_with('\t') && !line.trim_start().starts_with('.'))
671 .map(|line| line.trim_start())
672 .collect()
673 }
674
675 #[test]
676 fn an_instruction_is_written_the_way_the_target_says_it_is() {
677 let text = write(|func, names| {
678 let block = func.create_block();
679 let add = Opcode::new(names.intern("x64.add_rr_32"));
680 func.build(block, add)
681 .operand(Operand::write(Reg::physical(RAX), GPR))
682 .operand(Operand::read(Reg::physical(RAX), GPR))
683 .operand(Operand::read(Reg::physical(RCX), GPR))
684 .finish();
685 });
686 assert_eq!(body(&text), ["addl\t%ecx, %eax"]);
689 }
690
691 #[test]
692 fn an_opcode_the_machine_has_no_single_instruction_for_is_written_as_the_ones_it_has() {
693 let text = write(|func, names| {
694 let block = func.create_block();
695 let cmp = Opcode::new(names.intern("x64.cmp_set_l_64"));
696 func.build(block, cmp)
697 .operand(Operand::write(Reg::physical(RAX), GPR))
698 .operand(Operand::read(Reg::physical(RCX), GPR))
699 .operand(Operand::read(Reg::physical(RDX), GPR))
700 .finish();
701 });
702 assert_eq!(body(&text), ["cmpq\t%rdx, %rcx", "setl\t%al"]);
705 }
706
707 #[test]
708 fn an_opcode_that_is_not_an_instruction_is_written_as_nothing() {
709 let text = write(|func, names| {
710 let block = func.create_block();
711 let ret = Opcode::new(names.intern("x64.ret_val_32"));
712 func.build(block, ret).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
713 });
714 assert_eq!(body(&text), Vec::<&str>::new());
715 }
716
717 #[test]
718 fn an_alignment_is_written_as_the_directive_that_asks_for_it() {
719 let text = write(|func, names| {
720 let block = func.create_block();
721 let align = Opcode::new(names.intern("x64.align"));
722 func.build(block, align).imm(32).finish();
723 });
724 assert!(text.contains("\n\t.p2align\t5, 0x90\n"), "{text}");
729 assert_eq!(body(&text), Vec::<&str>::new());
730 }
731
732 #[test]
733 fn an_address_is_a_displacement_and_then_the_registers_it_names() {
734 let text = write(|func, names| {
735 let block = func.create_block();
736 let lea = Opcode::new(names.intern("x64.lea_64"));
737 func.build(block, lea)
738 .operand(Operand::write(Reg::physical(RAX), GPR))
739 .mem(
740 Mem::at(Operand::read(Reg::physical(RCX), GPR))
741 .indexed(Operand::read(Reg::physical(RDX), GPR), 4)
742 .plus(-16),
743 )
744 .finish();
745 });
746 assert_eq!(body(&text), ["leaq\t-16(%rcx,%rdx,4), %rax"]);
747 }
748
749 #[test]
750 fn an_address_in_a_thread_s_own_block_names_the_segment_and_no_register() {
751 let text = write(|func, names| {
752 let block = func.create_block();
753 let load = Opcode::new(names.intern("x64.mov_rm_64"));
754 func.build(block, load)
755 .operand(Operand::write(Reg::physical(RAX), GPR))
756 .mem(Mem::in_segment(Segment::Fs, 40))
757 .finish();
758 });
759 assert_eq!(body(&text), ["movq\t%fs:40, %rax"]);
763 }
764
765 #[test]
766 fn the_touch_a_probing_prologue_writes_is_an_immediate_and_then_an_address() {
767 let text = write(|func, names| {
768 let block = func.create_block();
769 let touch = Opcode::new(names.intern("x64.or_mi_8"));
770 func.build(block, touch)
771 .imm(0)
772 .mem(Mem::at(Operand::read(Reg::physical(RSP), GPR)))
773 .finish();
774 });
775 assert_eq!(body(&text), ["orb\t$0, (%rsp)"]);
779 }
780
781 #[test]
782 fn an_address_with_nothing_but_a_symbol_in_it_is_relative_to_the_instruction_pointer() {
783 let text = write(|func, names| {
784 let block = func.create_block();
785 let load = Opcode::new(names.intern("x64.mov_rm_64"));
786 let global = names.intern("counter");
787 func.build(block, load)
788 .operand(Operand::write(Reg::physical(RAX), GPR))
789 .mem(Mem::of(global))
790 .finish();
791 });
792 assert_eq!(body(&text), ["movq\tcounter(%rip), %rax"]);
793 }
794
795 #[test]
796 fn an_address_with_a_label_in_it_is_the_label_and_is_relative_as_well() {
797 let text = write(|func, names| {
798 let head = func.create_block();
799 let there = func.create_block();
800 let lea = Opcode::new(names.intern("x64.lea_64"));
801 let jump = Opcode::new(names.intern("x64.jmp_reg"));
802 func.build(head, lea)
803 .operand(Operand::write(Reg::physical(RAX), GPR))
804 .mem(Mem::block(there))
805 .finish();
806 func.build(head, jump).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
807 func.build(there, Opcode::new(names.intern("x64.ret"))).finish();
808 });
809 assert_eq!(body(&text), ["leaq\t.Lf_1(%rip), %rax", "jmp\t*%rax", "ret"]);
813 }
814
815 #[test]
816 fn an_address_that_reads_the_offset_table_says_so_on_the_symbol() {
817 let text = write(|func, names| {
818 let block = func.create_block();
819 let load = Opcode::new(names.intern("x64.mov_rm_64"));
820 let away = names.intern("away");
821 func.build(block, load)
822 .operand(Operand::write(Reg::physical(RAX), GPR))
823 .mem(Mem::got(away))
824 .finish();
825 });
826 assert_eq!(body(&text), ["movq\taway@GOTPCREL(%rip), %rax"]);
830 }
831
832 #[test]
833 fn an_address_that_reads_the_offset_of_a_thread_local_says_so_on_the_symbol_as_well() {
834 let text = write(|func, names| {
835 let block = func.create_block();
836 let load = Opcode::new(names.intern("x64.mov_rm_64"));
837 let away = names.intern("away");
838 func.build(block, load)
839 .operand(Operand::write(Reg::physical(RAX), GPR))
840 .mem(Mem::thread(away))
841 .finish();
842 });
843 assert_eq!(body(&text), ["movq\taway@GOTTPOFF(%rip), %rax"]);
847 }
848
849 #[test]
850 fn a_jump_goes_to_the_label_of_the_block_the_first_arm_names() {
851 let mut names = Interner::new();
852 let mut func = Func::new(names.intern("f"));
853 let first = func.create_block();
854 let second = func.create_block();
855 let jmp = Opcode::new(names.intern("x64.jmp"));
856 func.build(first, jmp).finish();
857 func.succs_mut(first).push(rucc_mir::BlockCall::to(second));
858 let text = print(
859 &[func],
860 &Globals::default(),
861 &[],
862 &names,
863 &target(Os::Linux),
864 true,
865 Output::default(),
866 )
867 .expect("a function of two blocks");
868 assert!(text.contains("\tjmp\t.Lf_1\n"), "{text}");
869 assert!(text.contains("\n.Lf_1:\n"), "{text}");
870 }
871
872 #[test]
873 fn a_symbol_is_spelled_the_way_the_object_format_spells_one() {
874 let mut names = Interner::new();
875 let mut func = Func::new(names.intern("f"));
876 let block = func.create_block();
877 let call = Opcode::new(names.intern("x64.call"));
878 let callee = names.intern("puts");
879 func.build(block, call).symbol(callee).finish();
880
881 let elf = print(
882 std::slice::from_ref(&func),
883 &Globals::default(),
884 &[],
885 &names,
886 &target(Os::Linux),
887 true,
888 Output::default(),
889 )
890 .expect("elf");
891 assert!(elf.contains("\tcall\tputs\n"), "{elf}");
892 assert!(elf.contains("\n.Lf_0:\n"), "{elf}");
893
894 let macho = print(
897 &[func],
898 &Globals::default(),
899 &[],
900 &names,
901 &target(Os::Darwin),
902 true,
903 Output::default(),
904 )
905 .expect("mach-o");
906 assert!(macho.contains("\tcall\t_puts\n"), "{macho}");
907 assert!(macho.contains("\n_f:\n"), "{macho}");
908 assert!(macho.contains("\nLf_0:\n"), "{macho}");
909 }
910
911 #[test]
912 fn a_function_that_was_never_allocated_is_refused_rather_than_written_wrongly() {
913 let mut names = Interner::new();
914 let mut func = Func::new(names.intern("f"));
915 let block = func.create_block();
916 let vreg = func.new_vreg(GPR);
917 let neg = Opcode::new(names.intern("x64.neg_r_32"));
918 func.build(block, neg).operand(Operand::write(vreg, GPR)).finish();
919 let error = print(
920 &[func],
921 &Globals::default(),
922 &[],
923 &names,
924 &target(Os::Linux),
925 true,
926 Output::default(),
927 )
928 .expect_err("a virtual register");
929 assert_eq!(
930 error,
931 Error::Virtual { func: "f".to_owned(), opcode: "x64.neg_r_32".to_owned() }
932 );
933 }
934
935 #[test]
936 fn an_opcode_the_target_does_not_describe_is_refused() {
937 let mut names = Interner::new();
938 let mut func = Func::new(names.intern("f"));
939 let block = func.create_block();
940 let made_up = Opcode::new(names.intern("x64.frobnicate"));
941 func.build(block, made_up).finish();
942 let error = print(
943 &[func],
944 &Globals::default(),
945 &[],
946 &names,
947 &target(Os::Linux),
948 true,
949 Output::default(),
950 )
951 .expect_err("no such instruction");
952 assert_eq!(
953 error,
954 Error::Opcode { func: "f".to_owned(), opcode: "x64.frobnicate".to_owned() }
955 );
956 }
957
958 #[test]
959 fn a_function_no_other_file_can_see_is_not_announced_to_the_linker() {
960 let mut names = Interner::new();
961 let mut hidden = Func::new(names.intern("hidden"));
962 hidden.binding = rucc_mir::Binding::Local;
963 hidden.create_block();
964 let text = print(
965 &[hidden],
966 &Globals::default(),
967 &[],
968 &names,
969 &target(Os::Linux),
970 true,
971 Output::default(),
972 )
973 .expect("elf");
974 assert!(text.contains("\nhidden:\n"), "{text}");
977 assert!(text.contains("\t.type\thidden, @function\n"), "{text}");
978 assert!(!text.contains(".globl"), "{text}");
980 }
981
982 #[test]
983 fn a_function_that_may_lose_to_another_definition_is_written_weak() {
984 let mut names = Interner::new();
985 let mut shared = Func::new(names.intern("shared"));
986 shared.binding = rucc_mir::Binding::Weak;
987 shared.create_block();
988 let text = print(
989 &[shared],
990 &Globals::default(),
991 &[],
992 &names,
993 &target(Os::Linux),
994 true,
995 Output::default(),
996 )
997 .expect("elf");
998 assert!(text.contains("\t.weak\tshared\n"), "{text}");
999 assert!(!text.contains(".globl"), "{text}");
1000 }
1001
1002 #[test]
1006 fn a_second_name_is_a_binding_and_a_set_and_nothing_else() {
1007 let names = Interner::new();
1008 let aliases = [
1009 Alias {
1010 name: "b".to_owned(),
1011 target: "a".to_owned(),
1012 binding: Binding::Global,
1013 visibility: Visibility::Default,
1014 },
1015 Alias {
1016 name: "c".to_owned(),
1017 target: "a".to_owned(),
1018 binding: Binding::Weak,
1019 visibility: Visibility::Default,
1020 },
1021 Alias {
1022 name: "d".to_owned(),
1023 target: "a".to_owned(),
1024 binding: Binding::Local,
1025 visibility: Visibility::Default,
1026 },
1027 ];
1028 let vars = vec![var("a", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])])];
1029 let text = print(
1030 &[],
1031 &Globals { vars, weak: Vec::new() },
1032 &aliases,
1033 &names,
1034 &target(Os::Linux),
1035 true,
1036 Output::default(),
1037 )
1038 .expect("a machine with a writer");
1039 assert!(text.contains("\t.globl\tb\n\t.set\tb,a\n"), "{text}");
1040 assert!(text.contains("\t.weak\tc\n\t.set\tc,a\n"), "{text}");
1041 assert!(text.contains("\t.set\td,a\n"), "{text}");
1044 assert!(!text.contains("\t.type\tb"), "the type comes from what it points at: {text}");
1045 assert!(!text.contains("\t.size\tb"), "and so does the size: {text}");
1046 assert_eq!(text.matches(".long\t1").count(), 1, "{text}");
1049 }
1050
1051 #[test]
1052 fn a_variable_is_a_section_a_name_and_the_bytes_between_them() {
1053 let text = data(
1054 vec![var("counter", Place::Written, vec![Piece::Scalar(vec![42, 0, 0, 0])])],
1055 Os::Linux,
1056 );
1057 assert!(text.contains("\t.data\n"), "{text}");
1058 assert!(text.contains("\t.globl\tcounter\n"), "{text}");
1059 assert!(text.contains("\t.p2align\t2\n"), "{text}");
1060 assert!(text.contains("\t.type\tcounter, @object\n"), "{text}");
1061 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
1064 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
1065 }
1066
1067 #[test]
1070 fn a_thread_local_variable_is_a_section_with_the_flag_on_it_and_a_type_of_its_own() {
1071 let text = data(
1072 vec![var(
1073 "counter",
1074 Place::Thread { zero: false },
1075 vec![Piece::Scalar(vec![42, 0, 0, 0])],
1076 )],
1077 Os::Linux,
1078 );
1079 assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
1080 assert!(text.contains("\t.type\tcounter, @tls_object\n"), "{text}");
1081 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
1082 }
1083
1084 #[test]
1086 fn a_thread_local_variable_with_no_image_to_carry_goes_in_the_section_that_carries_none() {
1087 let text = data(
1088 vec![var("counter", Place::Thread { zero: true }, vec![Piece::Zero(4)])],
1089 Os::Linux,
1090 );
1091 assert!(text.contains("\t.section\t.tbss,\"awT\",@nobits\n"), "{text}");
1092 assert!(text.contains("\t.type\tcounter, @tls_object\n"), "{text}");
1093 assert!(text.contains("\ncounter:\n\t.space\t4\n"), "{text}");
1094 }
1095
1096 #[test]
1097 fn a_variable_no_other_file_can_see_is_not_announced_to_the_linker() {
1098 let mut hidden = var("hidden", Place::Zero, vec![Piece::Zero(4)]);
1099 hidden.binding = Binding::Local;
1100 let text = data(vec![hidden], Os::Linux);
1101 assert!(text.contains("\t.bss\n"), "{text}");
1102 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
1103 assert!(!text.contains(".globl"), "{text}");
1106 }
1107
1108 #[test]
1109 fn a_tentative_definition_is_a_request_rather_than_a_section_and_a_label() {
1110 let text = data(vec![var("x", Place::Merged, vec![Piece::Zero(4)])], Os::Linux);
1111 assert_eq!(text.lines().find(|line| line.contains(".comm")), Some("\t.comm\tx,4,4"));
1112 assert!(!text.contains("\nx:\n"), "nothing here says where it is: {text}");
1113 }
1114
1115 #[test]
1122 fn every_function_gets_a_section_of_its_own_when_that_is_what_was_asked_for() {
1123 let text = split_code("first", "second", Os::Linux);
1124 assert!(text.starts_with("\t.text\n"), "{text}");
1125 assert!(text.contains("\t.section\t.text.first,\"ax\",@progbits\n"), "{text}");
1126 assert!(text.contains("\t.section\t.text.second,\"ax\",@progbits\n"), "{text}");
1127 let opened = text.find(".section\t.text.first").expect("a section");
1130 assert!(opened < text.find("\nfirst:\n").expect("a label"), "{text}");
1131 let plain = write(|_, _| {});
1133 assert!(!plain.contains(".text."), "{plain}");
1134 }
1135
1136 #[test]
1140 fn a_format_that_already_lets_the_linker_split_a_section_is_not_asked_to_split_it_again() {
1141 let text = split_code("first", "second", Os::Darwin);
1142 assert!(text.contains("\t.subsections_via_symbols\n"), "{text}");
1143 assert_eq!(text.matches(".section").count(), 1, "the one it opens with: {text}");
1144 let vars = vec![var("counter", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])])];
1145 assert_eq!(split(vars.clone(), Os::Darwin), data(vars, Os::Darwin));
1146 }
1147
1148 #[test]
1152 fn every_variable_gets_a_section_named_after_it_when_that_is_what_was_asked_for() {
1153 let vars = vec![
1154 var("g", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])]),
1155 var("z", Place::Zero, vec![Piece::Zero(4)]),
1156 var("r", Place::ReadOnly, vec![Piece::Scalar(vec![3, 0, 0, 0])]),
1157 ];
1158 let text = split(vars.clone(), Os::Linux);
1159 assert!(text.contains("\t.section\t.data.g,\"aw\"\n\t.globl\tg\n"), "{text}");
1160 assert!(text.contains("\t.section\t.bss.z,\"aw\",@nobits\n"), "{text}");
1161 assert!(text.contains("\t.section\t.rodata.r,\"a\"\n"), "{text}");
1162 assert!(text.contains("\ng:\n\t.long\t1\n"), "{text}");
1165 assert!(text.contains("\t.size\tg, .-g\n"), "{text}");
1166 assert!(text.contains("\t.space\t4\n"), "{text}");
1167 assert!(!text.contains(".text."), "{text}");
1170 let plain = data(vars, Os::Linux);
1171 assert!(plain.contains("\t.data\n") && plain.contains("\t.bss\n"), "{plain}");
1172 assert!(!plain.contains(".data.g"), "{plain}");
1173 }
1174
1175 #[test]
1176 fn the_object_format_decides_how_a_variable_is_written_as_much_as_a_function() {
1177 let text = data(vec![var("x", Place::Zero, vec![Piece::Zero(4)])], Os::Darwin);
1178 assert!(text.contains("\t.zerofill\t__DATA,__bss,_x,4,2\n"), "{text}");
1181 let read_only = data(vec![var("x", Place::ReadOnly, vec![Piece::Zero(4)])], Os::Darwin);
1182 assert!(read_only.contains("\t.section\t__TEXT,__const\n"), "{read_only}");
1183 assert!(read_only.contains("\n_x:\n"), "the underscore, without which nothing links");
1184 }
1185
1186 #[test]
1187 fn a_run_of_bytes_is_written_so_that_it_reads_back_as_the_same_bytes() {
1188 let bytes = Piece::Bytes(b"a\"b\\\n\0\x801".to_vec());
1189 let text = data(vec![var("s", Place::ReadOnly, vec![bytes])], Os::Linux);
1190 assert!(text.contains("\t.ascii\t\"a\\\"b\\\\\\012\\000\\2001\"\n"), "{text}");
1193 }
1194
1195 #[test]
1196 fn the_address_of_a_name_in_an_image_is_written_as_the_name() {
1197 let addr = Piece::Addr { symbol: "y".to_owned(), addend: 16, bytes: 8 };
1198 let text = data(vec![var("p", Place::Written, vec![addr])], Os::Linux);
1199 assert!(text.contains("\np:\n\t.quad\ty+16\n"), "{text}");
1200 }
1201
1202 #[test]
1203 fn a_distance_in_an_image_is_written_as_the_name_less_where_it_is() {
1204 let away = Piece::Away { symbol: "y".to_owned(), addend: 0 };
1205 let text = data(vec![var("d", Place::ReadOnly, vec![away])], Os::Linux);
1206 assert!(text.contains("\nd:\n\t.long\ty - .\n"), "{text}");
1207
1208 let away = Piece::Away { symbol: "y".to_owned(), addend: -3 };
1209 let text = data(vec![var("d", Place::ReadOnly, vec![away])], Os::Linux);
1210 assert!(text.contains("\nd:\n\t.long\ty-3 - .\n"), "{text}");
1211 }
1212
1213 #[test]
1214 fn a_machine_with_no_writer_here_is_said_so_rather_than_written_as_x86_64() {
1215 let names = Interner::new();
1216 let aarch64 = TargetInfo::new(Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu));
1217 let error = print(&[], &Globals::default(), &[], &names, &aarch64, true, Output::default())
1218 .expect_err("no writer");
1219 assert!(matches!(error, Error::Machine { .. }), "{error:?}");
1220 }
1221}