1use std::fmt::Write as _;
44
45use rucc_base::Interner;
46use rucc_mir::{Amode, Block, CfiOp, Func, Inst, Opcode, Operand, 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 writer.directives.end(&mut writer.out, property);
117 Ok(writer.out)
118}
119
120struct Writer<'a> {
122 names: &'a Interner,
123 directives: Directives,
124 unwind: bool,
126 out: String,
127 labels: Vec<u32>,
130 sections: Sections,
132}
133
134impl Writer<'_> {
135 fn func(&mut self, func: &Func) -> Result<(), Error> {
137 let name = self.names.resolve(func.name).to_owned();
138 self.number(func);
139 let binding = binding(func.binding);
140 let seen = visibility(func.visibility);
141 let align = func.align.unwrap_or(FUNC_ALIGN);
142 self.directives.code(&mut self.out, &name, self.sections);
143 let patch =
147 func.patch.map(|patch| (patch, format!("{}pfe_{name}", self.directives.local())));
148 let mut ahead = String::new();
149 if let Some((patch, label)) = &patch {
150 let back = if self.sections.functions {
151 format!("\t.section\t.text.{name}")
152 } else {
153 self.directives.text().to_owned()
154 };
155 self.directives.patchable(&mut ahead, label, &back);
156 if patch.before > 0 {
157 let _ = writeln!(ahead, "{label}:");
158 self.pad(&mut ahead, patch.pad, patch.before);
159 }
160 }
161 self.directives.open(&mut self.out, &name, align, binding, seen, &ahead);
162 let unwind = self.unwind;
163 if unwind {
164 let _ = writeln!(self.out, "\t.cfi_startproc");
165 }
166 let end = func.cfi_end();
167 for (index, block) in func.blocks().enumerate() {
168 let _ = writeln!(self.out, "{}{name}_{index}:", self.directives.local());
169 for inst in func.insts(block) {
170 if let Some((patch, label)) = &patch {
176 if patch.before == 0 && patch.after == Some(inst) {
177 let _ = writeln!(self.out, "{label}:");
178 }
179 }
180 self.inst(func, block, inst, &name)?;
181 if unwind && Some(inst) != end {
182 for op in func.cfi_after(inst) {
183 self.cfi(op);
184 }
185 }
186 }
187 }
188 if unwind {
189 let _ = writeln!(self.out, "\t.cfi_endproc");
190 }
191 self.directives.close(&mut self.out, &name);
192 Ok(())
193 }
194
195 fn pad(&self, out: &mut String, pad: Opcode, count: u32) {
202 let spelled = self.names.resolve(pad.name());
203 let opcode = spelled.strip_prefix(PREFIX).unwrap_or(spelled);
204 for _ in 0..count {
205 let _ = writeln!(out, "\t{opcode}");
206 }
207 }
208
209 fn cfi(&mut self, op: CfiOp) {
216 let _ = match op {
217 CfiOp::DefCfa { reg, offset } => {
218 writeln!(self.out, "\t.cfi_def_cfa {reg}, {offset}")
219 }
220 CfiOp::DefCfaOffset(offset) => writeln!(self.out, "\t.cfi_def_cfa_offset {offset}"),
221 CfiOp::DefCfaRegister(reg) => writeln!(self.out, "\t.cfi_def_cfa_register {reg}"),
222 CfiOp::Offset { reg, offset } => writeln!(self.out, "\t.cfi_offset {reg}, {offset}"),
223 CfiOp::Restore(reg) => writeln!(self.out, "\t.cfi_restore {reg}"),
224 CfiOp::RememberState => writeln!(self.out, "\t.cfi_remember_state"),
225 CfiOp::RestoreState => writeln!(self.out, "\t.cfi_restore_state"),
226 };
227 }
228
229 fn variable(&mut self, var: &Variable) {
231 if !self.directives.variable(&mut self.out, var, self.sections) {
232 return;
233 }
234 for piece in &var.pieces {
235 self.piece(piece);
236 }
237 self.directives.close(&mut self.out, &var.name);
238 }
239
240 fn piece(&mut self, piece: &Piece) {
246 match piece {
247 Piece::Zero(bytes) => {
251 let _ = writeln!(self.out, "\t.space\t{bytes}");
252 }
253 Piece::Bytes(bytes) => {
254 let _ = writeln!(self.out, "\t.ascii\t\"{}\"", escape(bytes));
255 }
256 Piece::Scalar(bytes) => match width(bytes.len()) {
257 Some(directive) => {
258 let mut value = [0u8; 16];
259 value[..bytes.len()].copy_from_slice(bytes);
260 let _ = writeln!(self.out, "\t{directive}\t{}", u128::from_le_bytes(value));
261 }
262 None => {
265 let list = bytes.iter().map(u8::to_string).collect::<Vec<_>>().join(", ");
266 let _ = writeln!(self.out, "\t.byte\t{list}");
267 }
268 },
269 Piece::Addr { symbol, addend, bytes } => {
272 let directive = if *bytes == 8 { ".quad" } else { ".long" };
273 let name = format!("{}{symbol}", self.directives.symbol());
274 match addend {
275 0 => {
276 let _ = writeln!(self.out, "\t{directive}\t{name}");
277 }
278 _ => {
279 let sign = if *addend < 0 { '-' } else { '+' };
280 let _ = writeln!(self.out, "\t{directive}\t{name}{sign}{}", addend.abs());
281 }
282 }
283 }
284 }
285 }
286
287 fn number(&mut self, func: &Func) {
289 self.labels.clear();
290 self.labels.resize(func.block_count(), u32::MAX);
291 for (index, block) in func.blocks().enumerate() {
292 self.labels[block.index()] = u32::try_from(index).expect("a block number");
293 }
294 }
295
296 fn inst(
298 &mut self,
299 func: &Func,
300 block: Block,
301 inst: Inst,
302 func_name: &str,
303 ) -> Result<(), Error> {
304 let data = func[inst];
305 let spelled = self.names.resolve(data.opcode.name());
306 let opcode = spelled.strip_prefix(PREFIX).unwrap_or(spelled);
307 let Some(written) = x86_64::written(opcode) else {
308 return Err(Error::Opcode { func: func_name.to_owned(), opcode: spelled.to_owned() });
309 };
310 let operands = &func[data.operands];
311 for machine in written {
312 let mut args = Vec::with_capacity(machine.args.len());
313 for arg in machine.args {
314 args.push(match *arg {
315 Arg::Reg(at, width) => {
316 let operand = operands[usize::from(at)];
317 self.reg(operand, width, func_name, spelled)?
318 }
319 Arg::Xmm(at) => {
323 let operand = operands[usize::from(at)];
324 self.reg(operand, Width::Quad, func_name, spelled)?
325 }
326 Arg::Named(register) => format!("%{register}"),
327 Arg::Stack(depth) => format!("%st({depth})"),
332 Arg::Through => {
335 let operand = operands[defs(operands)];
336 format!("*{}", self.reg(operand, Width::Quad, func_name, spelled)?)
337 }
338 Arg::Imm => match data.imm {
339 Some(imm) => format!("${}", func[imm].0),
340 None => "$0".to_owned(),
341 },
342 Arg::Mem => match data.mem {
343 Some(mem) => self.amode(operands, &func[mem], func_name, spelled)?,
344 None => "0".to_owned(),
345 },
346 Arg::Symbol => match data.symbol {
347 Some(symbol) => {
348 format!("{}{}", self.directives.symbol(), self.names.resolve(symbol))
349 }
350 None => "0".to_owned(),
351 },
352 Arg::Label => match func[block].succs.first() {
356 Some(call) => self.label(func_name, call.block),
357 None => "0".to_owned(),
358 },
359 });
360 }
361 if args.is_empty() {
362 let _ = writeln!(self.out, "\t{}", machine.mnemonic);
363 } else {
364 let _ = writeln!(self.out, "\t{}\t{}", machine.mnemonic, args.join(", "));
365 }
366 }
367 Ok(())
368 }
369
370 fn reg(
372 &self,
373 operand: Operand,
374 width: Width,
375 func_name: &str,
376 opcode: &str,
377 ) -> Result<String, Error> {
378 let Some(phys) = operand.reg.phys() else {
379 return Err(Error::Virtual { func: func_name.to_owned(), opcode: opcode.to_owned() });
380 };
381 Ok(format!("%{}", name_of(operand.class, phys, width)))
382 }
383
384 fn amode(
390 &self,
391 operands: &[Operand],
392 amode: &Amode,
393 func_name: &str,
394 opcode: &str,
395 ) -> Result<String, Error> {
396 let mut out = String::new();
397 match amode.segment {
400 Some(Segment::Fs) => out.push_str("%fs:"),
401 Some(Segment::Gs) => out.push_str("%gs:"),
402 None => {}
403 }
404 if let Some(symbol) = amode.symbol {
405 let _ = write!(out, "{}{}", self.directives.symbol(), self.names.resolve(symbol));
406 if amode.got {
410 out.push_str("@GOTPCREL");
411 }
412 if amode.disp != 0 {
413 let sign = if amode.disp < 0 { '-' } else { '+' };
414 let _ = write!(out, "{sign}{}", i64::from(amode.disp).abs());
415 }
416 } else if amode.disp != 0 || (amode.base.is_none() && amode.index.is_none()) {
417 let _ = write!(out, "{}", amode.disp);
420 }
421 let base = amode.base.and_then(|at| operands.get(usize::from(at)));
422 let index = amode.index.and_then(|at| operands.get(usize::from(at)));
423 if base.is_some() || index.is_some() {
424 out.push('(');
425 if let Some(operand) = base {
426 out.push_str(&self.reg(*operand, Width::Quad, func_name, opcode)?);
427 }
428 if let Some(operand) = index {
429 let reg = self.reg(*operand, Width::Quad, func_name, opcode)?;
430 let _ = write!(out, ",{reg},{}", amode.scale);
431 }
432 out.push(')');
433 } else if amode.symbol.is_some() {
434 out.push_str("(%rip)");
435 }
436 Ok(out)
437 }
438
439 fn label(&self, func_name: &str, block: Block) -> String {
441 match self.labels.get(block.index()).copied() {
442 Some(u32::MAX) | None => format!("{}{func_name}_?", self.directives.local()),
443 Some(number) => format!("{}{func_name}_{number}", self.directives.local()),
444 }
445 }
446}
447
448fn width(bytes: usize) -> Option<&'static str> {
450 match bytes {
451 1 => Some(".byte"),
452 2 => Some(".short"),
453 4 => Some(".long"),
454 8 => Some(".quad"),
455 _ => None,
456 }
457}
458
459fn escape(bytes: &[u8]) -> String {
465 let mut out = String::with_capacity(bytes.len());
466 for byte in bytes {
467 match byte {
468 b'"' => out.push_str("\\\""),
469 b'\\' => out.push_str("\\\\"),
470 0x20..=0x7e => out.push(char::from(*byte)),
471 _ => {
472 let _ = write!(out, "\\{byte:03o}");
473 }
474 }
475 }
476 out
477}
478
479fn name_of(class: RegClass, reg: PhysReg, width: Width) -> &'static str {
484 let named = if class == x86_64::GPR {
485 x86_64::gpr_name(reg, width)
486 } else {
487 x86_64::REGS.name(class, reg)
488 };
489 named.unwrap_or("?")
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495
496 use rucc_base::Interner;
497 use rucc_mir::{Func, Mem, Operand, Reg};
498 use rucc_object::{Binding, Place, Visibility};
499 use rucc_target::x86_64::{GPR, RAX, RCX, RDX, RSP};
500 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
501
502 fn target(os: Os) -> TargetInfo {
504 TargetInfo::new(Triple::new(Arch::X86_64, os, Env::Gnu))
505 }
506
507 fn write(build: impl FnOnce(&mut Func, &mut Interner)) -> String {
509 let mut names = Interner::new();
510 let mut func = Func::new(names.intern("f"));
511 build(&mut func, &mut names);
512 print(
513 &[func],
514 &Globals::default(),
515 &[],
516 &names,
517 &target(Os::Linux),
518 true,
519 Output::default(),
520 )
521 .expect("a function that was allocated")
522 }
523
524 fn data(vars: Vec<Variable>, os: Os) -> String {
526 let names = Interner::new();
527 print(&[], &Globals { vars }, &[], &names, &target(os), true, Output::default())
528 .expect("a machine with a writer")
529 }
530
531 fn split(vars: Vec<Variable>, os: Os) -> String {
533 let names = Interner::new();
534 let sections =
535 Output { sections: Sections { functions: false, data: true }, ..Output::default() };
536 print(&[], &Globals { vars }, &[], &names, &target(os), true, sections)
537 .expect("a machine with a writer")
538 }
539
540 fn split_code(first: &str, second: &str, os: Os) -> String {
542 let mut names = Interner::new();
543 let mut funcs = Vec::new();
544 for name in [first, second] {
545 let mut func = Func::new(names.intern(name));
546 func.create_block();
547 funcs.push(func);
548 }
549 let sections =
550 Output { sections: Sections { functions: true, data: false }, ..Output::default() };
551 print(&funcs, &Globals::default(), &[], &names, &target(os), true, sections)
552 .expect("a machine with a writer")
553 }
554
555 fn var(name: &str, place: Place, pieces: Vec<Piece>) -> Variable {
557 Variable {
558 name: name.to_owned(),
559 size: 4,
560 align: 4,
561 place,
562 binding: Binding::Global,
563 visibility: Visibility::Default,
564 pieces,
565 }
566 }
567
568 fn body(text: &str) -> Vec<&str> {
570 text.lines()
571 .filter(|line| line.starts_with('\t') && !line.trim_start().starts_with('.'))
572 .map(|line| line.trim_start())
573 .collect()
574 }
575
576 #[test]
577 fn an_instruction_is_written_the_way_the_target_says_it_is() {
578 let text = write(|func, names| {
579 let block = func.create_block();
580 let add = Opcode::new(names.intern("x64.add_rr_32"));
581 func.build(block, add)
582 .operand(Operand::write(Reg::physical(RAX), GPR))
583 .operand(Operand::read(Reg::physical(RAX), GPR))
584 .operand(Operand::read(Reg::physical(RCX), GPR))
585 .finish();
586 });
587 assert_eq!(body(&text), ["addl\t%ecx, %eax"]);
590 }
591
592 #[test]
593 fn an_opcode_the_machine_has_no_single_instruction_for_is_written_as_the_ones_it_has() {
594 let text = write(|func, names| {
595 let block = func.create_block();
596 let cmp = Opcode::new(names.intern("x64.cmp_set_l_64"));
597 func.build(block, cmp)
598 .operand(Operand::write(Reg::physical(RAX), GPR))
599 .operand(Operand::read(Reg::physical(RCX), GPR))
600 .operand(Operand::read(Reg::physical(RDX), GPR))
601 .finish();
602 });
603 assert_eq!(body(&text), ["cmpq\t%rdx, %rcx", "setl\t%al"]);
606 }
607
608 #[test]
609 fn an_opcode_that_is_not_an_instruction_is_written_as_nothing() {
610 let text = write(|func, names| {
611 let block = func.create_block();
612 let ret = Opcode::new(names.intern("x64.ret_val_32"));
613 func.build(block, ret).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
614 });
615 assert_eq!(body(&text), Vec::<&str>::new());
616 }
617
618 #[test]
619 fn an_address_is_a_displacement_and_then_the_registers_it_names() {
620 let text = write(|func, names| {
621 let block = func.create_block();
622 let lea = Opcode::new(names.intern("x64.lea_64"));
623 func.build(block, lea)
624 .operand(Operand::write(Reg::physical(RAX), GPR))
625 .mem(
626 Mem::at(Operand::read(Reg::physical(RCX), GPR))
627 .indexed(Operand::read(Reg::physical(RDX), GPR), 4)
628 .plus(-16),
629 )
630 .finish();
631 });
632 assert_eq!(body(&text), ["leaq\t-16(%rcx,%rdx,4), %rax"]);
633 }
634
635 #[test]
636 fn an_address_in_a_thread_s_own_block_names_the_segment_and_no_register() {
637 let text = write(|func, names| {
638 let block = func.create_block();
639 let load = Opcode::new(names.intern("x64.mov_rm_64"));
640 func.build(block, load)
641 .operand(Operand::write(Reg::physical(RAX), GPR))
642 .mem(Mem::in_segment(Segment::Fs, 40))
643 .finish();
644 });
645 assert_eq!(body(&text), ["movq\t%fs:40, %rax"]);
649 }
650
651 #[test]
652 fn the_touch_a_probing_prologue_writes_is_an_immediate_and_then_an_address() {
653 let text = write(|func, names| {
654 let block = func.create_block();
655 let touch = Opcode::new(names.intern("x64.or_mi_8"));
656 func.build(block, touch)
657 .imm(0)
658 .mem(Mem::at(Operand::read(Reg::physical(RSP), GPR)))
659 .finish();
660 });
661 assert_eq!(body(&text), ["orb\t$0, (%rsp)"]);
665 }
666
667 #[test]
668 fn an_address_with_nothing_but_a_symbol_in_it_is_relative_to_the_instruction_pointer() {
669 let text = write(|func, names| {
670 let block = func.create_block();
671 let load = Opcode::new(names.intern("x64.mov_rm_64"));
672 let global = names.intern("counter");
673 func.build(block, load)
674 .operand(Operand::write(Reg::physical(RAX), GPR))
675 .mem(Mem::of(global))
676 .finish();
677 });
678 assert_eq!(body(&text), ["movq\tcounter(%rip), %rax"]);
679 }
680
681 #[test]
682 fn an_address_that_reads_the_offset_table_says_so_on_the_symbol() {
683 let text = write(|func, names| {
684 let block = func.create_block();
685 let load = Opcode::new(names.intern("x64.mov_rm_64"));
686 let away = names.intern("away");
687 func.build(block, load)
688 .operand(Operand::write(Reg::physical(RAX), GPR))
689 .mem(Mem::got(away))
690 .finish();
691 });
692 assert_eq!(body(&text), ["movq\taway@GOTPCREL(%rip), %rax"]);
696 }
697
698 #[test]
699 fn a_jump_goes_to_the_label_of_the_block_the_first_arm_names() {
700 let mut names = Interner::new();
701 let mut func = Func::new(names.intern("f"));
702 let first = func.create_block();
703 let second = func.create_block();
704 let jmp = Opcode::new(names.intern("x64.jmp"));
705 func.build(first, jmp).finish();
706 func.succs_mut(first).push(rucc_mir::BlockCall::to(second));
707 let text = print(
708 &[func],
709 &Globals::default(),
710 &[],
711 &names,
712 &target(Os::Linux),
713 true,
714 Output::default(),
715 )
716 .expect("a function of two blocks");
717 assert!(text.contains("\tjmp\t.Lf_1\n"), "{text}");
718 assert!(text.contains("\n.Lf_1:\n"), "{text}");
719 }
720
721 #[test]
722 fn a_symbol_is_spelled_the_way_the_object_format_spells_one() {
723 let mut names = Interner::new();
724 let mut func = Func::new(names.intern("f"));
725 let block = func.create_block();
726 let call = Opcode::new(names.intern("x64.call"));
727 let callee = names.intern("puts");
728 func.build(block, call).symbol(callee).finish();
729
730 let elf = print(
731 std::slice::from_ref(&func),
732 &Globals::default(),
733 &[],
734 &names,
735 &target(Os::Linux),
736 true,
737 Output::default(),
738 )
739 .expect("elf");
740 assert!(elf.contains("\tcall\tputs\n"), "{elf}");
741 assert!(elf.contains("\n.Lf_0:\n"), "{elf}");
742
743 let macho = print(
746 &[func],
747 &Globals::default(),
748 &[],
749 &names,
750 &target(Os::Darwin),
751 true,
752 Output::default(),
753 )
754 .expect("mach-o");
755 assert!(macho.contains("\tcall\t_puts\n"), "{macho}");
756 assert!(macho.contains("\n_f:\n"), "{macho}");
757 assert!(macho.contains("\nLf_0:\n"), "{macho}");
758 }
759
760 #[test]
761 fn a_function_that_was_never_allocated_is_refused_rather_than_written_wrongly() {
762 let mut names = Interner::new();
763 let mut func = Func::new(names.intern("f"));
764 let block = func.create_block();
765 let vreg = func.new_vreg(GPR);
766 let neg = Opcode::new(names.intern("x64.neg_r_32"));
767 func.build(block, neg).operand(Operand::write(vreg, GPR)).finish();
768 let error = print(
769 &[func],
770 &Globals::default(),
771 &[],
772 &names,
773 &target(Os::Linux),
774 true,
775 Output::default(),
776 )
777 .expect_err("a virtual register");
778 assert_eq!(
779 error,
780 Error::Virtual { func: "f".to_owned(), opcode: "x64.neg_r_32".to_owned() }
781 );
782 }
783
784 #[test]
785 fn an_opcode_the_target_does_not_describe_is_refused() {
786 let mut names = Interner::new();
787 let mut func = Func::new(names.intern("f"));
788 let block = func.create_block();
789 let made_up = Opcode::new(names.intern("x64.frobnicate"));
790 func.build(block, made_up).finish();
791 let error = print(
792 &[func],
793 &Globals::default(),
794 &[],
795 &names,
796 &target(Os::Linux),
797 true,
798 Output::default(),
799 )
800 .expect_err("no such instruction");
801 assert_eq!(
802 error,
803 Error::Opcode { func: "f".to_owned(), opcode: "x64.frobnicate".to_owned() }
804 );
805 }
806
807 #[test]
808 fn a_function_no_other_file_can_see_is_not_announced_to_the_linker() {
809 let mut names = Interner::new();
810 let mut hidden = Func::new(names.intern("hidden"));
811 hidden.binding = rucc_mir::Binding::Local;
812 hidden.create_block();
813 let text = print(
814 &[hidden],
815 &Globals::default(),
816 &[],
817 &names,
818 &target(Os::Linux),
819 true,
820 Output::default(),
821 )
822 .expect("elf");
823 assert!(text.contains("\nhidden:\n"), "{text}");
826 assert!(text.contains("\t.type\thidden, @function\n"), "{text}");
827 assert!(!text.contains(".globl"), "{text}");
829 }
830
831 #[test]
832 fn a_function_that_may_lose_to_another_definition_is_written_weak() {
833 let mut names = Interner::new();
834 let mut shared = Func::new(names.intern("shared"));
835 shared.binding = rucc_mir::Binding::Weak;
836 shared.create_block();
837 let text = print(
838 &[shared],
839 &Globals::default(),
840 &[],
841 &names,
842 &target(Os::Linux),
843 true,
844 Output::default(),
845 )
846 .expect("elf");
847 assert!(text.contains("\t.weak\tshared\n"), "{text}");
848 assert!(!text.contains(".globl"), "{text}");
849 }
850
851 #[test]
855 fn a_second_name_is_a_binding_and_a_set_and_nothing_else() {
856 let names = Interner::new();
857 let aliases = [
858 Alias {
859 name: "b".to_owned(),
860 target: "a".to_owned(),
861 binding: Binding::Global,
862 visibility: Visibility::Default,
863 },
864 Alias {
865 name: "c".to_owned(),
866 target: "a".to_owned(),
867 binding: Binding::Weak,
868 visibility: Visibility::Default,
869 },
870 Alias {
871 name: "d".to_owned(),
872 target: "a".to_owned(),
873 binding: Binding::Local,
874 visibility: Visibility::Default,
875 },
876 ];
877 let vars = vec![var("a", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])])];
878 let text = print(
879 &[],
880 &Globals { vars },
881 &aliases,
882 &names,
883 &target(Os::Linux),
884 true,
885 Output::default(),
886 )
887 .expect("a machine with a writer");
888 assert!(text.contains("\t.globl\tb\n\t.set\tb,a\n"), "{text}");
889 assert!(text.contains("\t.weak\tc\n\t.set\tc,a\n"), "{text}");
890 assert!(text.contains("\t.set\td,a\n"), "{text}");
893 assert!(!text.contains("\t.type\tb"), "the type comes from what it points at: {text}");
894 assert!(!text.contains("\t.size\tb"), "and so does the size: {text}");
895 assert_eq!(text.matches(".long\t1").count(), 1, "{text}");
898 }
899
900 #[test]
901 fn a_variable_is_a_section_a_name_and_the_bytes_between_them() {
902 let text = data(
903 vec![var("counter", Place::Written, vec![Piece::Scalar(vec![42, 0, 0, 0])])],
904 Os::Linux,
905 );
906 assert!(text.contains("\t.data\n"), "{text}");
907 assert!(text.contains("\t.globl\tcounter\n"), "{text}");
908 assert!(text.contains("\t.p2align\t2\n"), "{text}");
909 assert!(text.contains("\t.type\tcounter, @object\n"), "{text}");
910 assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
913 assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
914 }
915
916 #[test]
917 fn a_variable_no_other_file_can_see_is_not_announced_to_the_linker() {
918 let mut hidden = var("hidden", Place::Zero, vec![Piece::Zero(4)]);
919 hidden.binding = Binding::Local;
920 let text = data(vec![hidden], Os::Linux);
921 assert!(text.contains("\t.bss\n"), "{text}");
922 assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
923 assert!(!text.contains(".globl"), "{text}");
926 }
927
928 #[test]
929 fn a_tentative_definition_is_a_request_rather_than_a_section_and_a_label() {
930 let text = data(vec![var("x", Place::Merged, vec![Piece::Zero(4)])], Os::Linux);
931 assert_eq!(text.lines().find(|line| line.contains(".comm")), Some("\t.comm\tx,4,4"));
932 assert!(!text.contains("\nx:\n"), "nothing here says where it is: {text}");
933 }
934
935 #[test]
942 fn every_function_gets_a_section_of_its_own_when_that_is_what_was_asked_for() {
943 let text = split_code("first", "second", Os::Linux);
944 assert!(text.starts_with("\t.text\n"), "{text}");
945 assert!(text.contains("\t.section\t.text.first,\"ax\",@progbits\n"), "{text}");
946 assert!(text.contains("\t.section\t.text.second,\"ax\",@progbits\n"), "{text}");
947 let opened = text.find(".section\t.text.first").expect("a section");
950 assert!(opened < text.find("\nfirst:\n").expect("a label"), "{text}");
951 let plain = write(|_, _| {});
953 assert!(!plain.contains(".text."), "{plain}");
954 }
955
956 #[test]
960 fn a_format_that_already_lets_the_linker_split_a_section_is_not_asked_to_split_it_again() {
961 let text = split_code("first", "second", Os::Darwin);
962 assert!(text.contains("\t.subsections_via_symbols\n"), "{text}");
963 assert_eq!(text.matches(".section").count(), 1, "the one it opens with: {text}");
964 let vars = vec![var("counter", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])])];
965 assert_eq!(split(vars.clone(), Os::Darwin), data(vars, Os::Darwin));
966 }
967
968 #[test]
972 fn every_variable_gets_a_section_named_after_it_when_that_is_what_was_asked_for() {
973 let vars = vec![
974 var("g", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])]),
975 var("z", Place::Zero, vec![Piece::Zero(4)]),
976 var("r", Place::ReadOnly, vec![Piece::Scalar(vec![3, 0, 0, 0])]),
977 ];
978 let text = split(vars.clone(), Os::Linux);
979 assert!(text.contains("\t.section\t.data.g,\"aw\"\n\t.globl\tg\n"), "{text}");
980 assert!(text.contains("\t.section\t.bss.z,\"aw\",@nobits\n"), "{text}");
981 assert!(text.contains("\t.section\t.rodata.r,\"a\"\n"), "{text}");
982 assert!(text.contains("\ng:\n\t.long\t1\n"), "{text}");
985 assert!(text.contains("\t.size\tg, .-g\n"), "{text}");
986 assert!(text.contains("\t.space\t4\n"), "{text}");
987 assert!(!text.contains(".text."), "{text}");
990 let plain = data(vars, Os::Linux);
991 assert!(plain.contains("\t.data\n") && plain.contains("\t.bss\n"), "{plain}");
992 assert!(!plain.contains(".data.g"), "{plain}");
993 }
994
995 #[test]
996 fn the_object_format_decides_how_a_variable_is_written_as_much_as_a_function() {
997 let text = data(vec![var("x", Place::Zero, vec![Piece::Zero(4)])], Os::Darwin);
998 assert!(text.contains("\t.zerofill\t__DATA,__bss,_x,4,2\n"), "{text}");
1001 let read_only = data(vec![var("x", Place::ReadOnly, vec![Piece::Zero(4)])], Os::Darwin);
1002 assert!(read_only.contains("\t.section\t__TEXT,__const\n"), "{read_only}");
1003 assert!(read_only.contains("\n_x:\n"), "the underscore, without which nothing links");
1004 }
1005
1006 #[test]
1007 fn a_run_of_bytes_is_written_so_that_it_reads_back_as_the_same_bytes() {
1008 let bytes = Piece::Bytes(b"a\"b\\\n\0\x801".to_vec());
1009 let text = data(vec![var("s", Place::ReadOnly, vec![bytes])], Os::Linux);
1010 assert!(text.contains("\t.ascii\t\"a\\\"b\\\\\\012\\000\\2001\"\n"), "{text}");
1013 }
1014
1015 #[test]
1016 fn the_address_of_a_name_in_an_image_is_written_as_the_name() {
1017 let addr = Piece::Addr { symbol: "y".to_owned(), addend: 16, bytes: 8 };
1018 let text = data(vec![var("p", Place::Written, vec![addr])], Os::Linux);
1019 assert!(text.contains("\np:\n\t.quad\ty+16\n"), "{text}");
1020 }
1021
1022 #[test]
1023 fn a_machine_with_no_writer_here_is_said_so_rather_than_written_as_x86_64() {
1024 let names = Interner::new();
1025 let aarch64 = TargetInfo::new(Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu));
1026 let error = print(&[], &Globals::default(), &[], &names, &aarch64, true, Output::default())
1027 .expect_err("no writer");
1028 assert!(matches!(error, Error::Machine { .. }), "{error:?}");
1029 }
1030}