Skip to main content

rucc_asm/
att.rs

1//! Machine functions as assembly text, in AT&T syntax.
2//!
3//! Design: `spec/11-asm-objects-debug.md` section 11.1, which asks that the text path and the
4//! binary path share one instruction description so they cannot disagree about what an
5//! instruction is. This is the text path, and the description is `rucc_target::x86_64`.
6//!
7//! So there is almost nothing about x86-64 in this file. What an opcode is called, how many
8//! instructions it really is, which operand each of them is given and how wide each of those is
9//! written are all read out of the target. What is here is the syntax: a register carries a `%`,
10//! an immediate carries a `$`, an address is a displacement in front of a parenthesised base and
11//! index, and the source is written before the destination.
12//!
13//! Intel syntax, which section 11.1 requires as an input and which `-masm=intel` will ask for as
14//! an output, is the other order, no sigils and a different spelling of an address. It is a
15//! second walk over the same description rather than a second description, and it is not written
16//! yet.
17//!
18//! # What a block is
19//!
20//! A label, and then the instructions in it. Where a block goes is on the block rather than on
21//! its terminator, so a jump has already been made into an instruction by the block layout by
22//! the time anything gets here: what is left is to give each block a name, and the name is local
23//! so that it leaves no symbol behind for a debugger to show as if it were a function.
24//!
25//! # What the unwinder is told
26//!
27//! Every ELF function is wrapped in `.cfi_startproc` and `.cfi_endproc`, including the ones with
28//! no rows in them. An unwinder that lands on an address with no record covering it has to give
29//! up, so a leaf that never moves the stack pointer still needs a record: the one the CIE hands
30//! it, which says the frame ends at `rsp+8` and the return address is the word below that, is
31//! already the right answer for such a function and the empty record is how it asks for it.
32//!
33//! A row written after the last instruction of the last block is dropped. It would describe an
34//! address at or past the end of the function, which is outside what the record covers, and the
35//! usual thing to find there is an epilogue putting back a state nothing is going to read.
36//!
37//! # What is not written
38//!
39//! An opcode that is not an instruction is written as nothing. Three of them exist to hold a
40//! value in a register until something reads it, which is a fact the allocator needed and the
41//! machine does not, and by here it has been acted on: the register in the operand is the answer.
42
43use 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
56/// The prefix every x86-64 opcode carries in the machine IR.
57///
58/// An opcode is a name and a machine IR that holds two machines' instructions would otherwise
59/// have two `add_rr_32` in it. The description in `rucc-target` is indexed without it, because
60/// there it is already known which machine is being described.
61const PREFIX: &str = "x64.";
62
63/// Every function and every variable, as assembly text.
64///
65/// The functions first and the variables after them, which is the order every toolchain writes a
66/// file in and the order a person reading one expects. The second names come last, because a
67/// `.set` says nothing until the thing it names has been written down.
68///
69/// `unwind` is whether a function is described to an unwinder, which is
70/// `rucc_session::Options::unwinds` and is asked of the build rather than worked out here, so that
71/// this and the byte writer cannot answer it differently for one function.
72///
73/// `output` is whether each function and each variable is given a section of its own and what the
74/// file says it was built to have checked, which are the other things about this listing the caller
75/// decides: everything else is worked out from the functions and the target.
76///
77/// # Errors
78///
79/// [`Error::Machine`] for an architecture nothing here writes, and the two internal errors for a
80/// function that should not have got this far. See [`Error`].
81pub 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        // Nothing outside ELF reads one of these, and the directives for the other two formats are
99        // not the same ones, so a request for a table there is a request for nothing.
100        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    // Last, which is where gcc puts them. Each is a name and no bytes, so there is nothing to open
117    // a section for and nothing to close.
118    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
125/// A file being written out.
126struct Writer<'a> {
127    names: &'a Interner,
128    directives: Directives,
129    /// Whether each function is wrapped in an unwind record.
130    unwind: bool,
131    out: String,
132    /// The number each block is written as, indexed by its own, which is its place in the layout
133    /// rather than the order somebody happened to create the blocks in.
134    labels: Vec<u32>,
135    /// Whether each function and each variable is given a section of its own.
136    sections: Sections,
137}
138
139impl Writer<'_> {
140    /// One function: what the assembler is told about it, then its blocks.
141    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        // What has to be written between what the assembler is told about the function and the
149        // function's own label, which is nothing at all unless a patcher was promised room in
150        // front of the label. See `patch`.
151        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            // And the name an image knows the block by, as a second label on the same address. The
175            // block's own label is written by this file and is a number, which is no good to a
176            // relocation in another section: what that names is a symbol, and the name here is the
177            // one the front end minted for it when it lowered the image.
178            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                // The other half of the room, which is named here rather than laid down here: the
183                // instructions it is made of are in the entry block like any others, and all that
184                // is missing is somewhere for the record to point. Named at the instruction rather
185                // than at the top of the block because a landing pad goes in front of it, and the
186                // room a patcher writes over does not include the pad.
187                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    /// The instructions that do nothing which go in front of a function's own label.
208    ///
209    /// Written from the opcode rather than through the machinery every other instruction goes
210    /// through, because these are the only instructions in a finished function that are not in a
211    /// block and so are not instructions the function holds. The opcode is one with no operands,
212    /// which is what makes writing the mnemonic and nothing else the whole of it.
213    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    /// One row of the unwind table, as the directive an assembler reads it as.
222    ///
223    /// The registers are written as numbers rather than as names, which is what gcc writes and
224    /// what avoids a second spelling of a register that could disagree with the first. They are
225    /// DWARF's numbers, which are not the machine's, and the one place the mapping lives is the
226    /// calling convention the prologue read it out of.
227    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    /// One variable: what the assembler is told about it, then its image.
242    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    /// One piece of an image, as the directive that says it.
253    ///
254    /// A number is written at the width it is rather than as the bytes it is made of, because the
255    /// point of a listing is to be read and `.long 258` is what a person wrote. The bytes are the
256    /// same either way, which is what [`crate::data`] is for.
257    fn piece(&mut self, piece: &Piece) {
258        match piece {
259            // `.space` rather than `.zero`, which every assembler also takes, because the
260            // directive set `spec/11-asm-objects-debug.md` says this compiler's own assembler
261            // reads has the one and not the other in it.
262            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                // A width no directive names, which on this machine is the eighty bit float and
275                // nothing else. Its bytes are what it is.
276                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            // Four and eight are the only widths that reach here, because the walk that built
282            // this refused every other one rather than leave the two halves to disagree.
283            // Written the way the template that asked for it wrote it, which is the one spelling
284            // an assembler reading this back would give the same relocation to.
285            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    /// Gives every block the number its label carries.
314    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    /// One instruction of the machine IR, as however many instructions of the machine it is.
323    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        // The one opcode that is not an instruction and is still written down. Everything below
334        // spells a mnemonic and its arguments, and this has neither: what it says is where the next
335        // instruction starts, which in a listing is the assembler's own directive. The fill byte is
336        // the one that does nothing, because a gap in the middle of a function is reached by falling
337        // into it rather than by jumping over it.
338        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        // The other one, which is the bytes a template wrote out as themselves. They come back the
351        // way they went in, since the directive is what the program wrote and an assembler reading
352        // this listing has to get the same bytes out of it. One directive, because that is how many
353        // the instruction carries.
354        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                    // A whole vector register, whose name the register file holds outright. The
380                    // width is asked for anyway because the one thing that reads it is the general
381                    // purpose file, and a register in any other class has one name.
382                    Arg::Xmm(at) => {
383                        let operand = operands[usize::from(at)];
384                        self.reg(operand, Width::Quad, func_name, spelled)?
385                    }
386                    // The two halves of one word, which is the one instruction that names part of
387                    // a register rather than an amount of it. The low half is the byte the name
388                    // above would give it and the high half is the one only four registers have,
389                    // which is why the operand is fixed to one of the four where it is described.
390                    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                    // A depth on the x87 stack rather than a register, which is why the number
406                    // comes from the table and not from an operand. The assembler writes the top
407                    // of the stack as `%st` on its own as well, and this writes `%st(0)` for it,
408                    // because one spelling for all eight is one thing fewer to know.
409                    Arg::Stack(depth) => format!("%st({depth})"),
410                    Arg::Lit(lane) => format!("${lane}"),
411                    // The first operand read, which is where a call puts the address it goes
412                    // through. Everything in front of it is a register the call writes.
413                    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                    // Where a conditional jump goes is the first arm, because the block layout
432                    // guarantees the second is the block laid out next and is fallen into. An
433                    // unconditional jump has one arm and it is the same one.
434                    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    /// One register operand, as much of it as the instruction reads or writes.
450    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    /// One address, which is a displacement and then whichever registers it names.
464    ///
465    /// A symbol with no base and no index is written relative to the instruction pointer, which
466    /// is how a global is reached in position independent code and is the only way this compiler
467    /// reaches one. A block is written the same way, and is the address a `&&label` produces.
468    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        // In front of everything, which is where an assembler wants it: the segment says which
477        // storage the rest of the address is counted in, so `%fs:40` reads left to right.
478        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            // The slot rather than the thing, which the assembler is told by the suffix and not by
486            // the instruction: the two are the same `movq` and differ only in what goes in the
487            // four bytes, so there is nowhere else to say it. The third one is a slot as well, and
488            // what it holds is an offset into a thread's own block rather than an address.
489            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            // A label of this function, which is written the way a symbol is and reached the way a
500            // symbol is, and is neither: what the assembler puts in the four bytes is a distance it
501            // works out itself, since both ends are in the section it is writing.
502            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            // A mode that names no register at all is an absolute address, and zero is one of
509            // them, so the number is written even when it is zero and there is nothing else.
510            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    /// The label one block of one function carries.
531    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
539/// The directive that writes a number that many bytes wide, and `None` for a width none does.
540fn 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
550/// Those bytes as the inside of a string an assembler reads back as the same bytes.
551///
552/// Everything outside printable ASCII is written as three octal digits rather than as itself,
553/// which is what keeps a string with a newline in it on one line and what stops a digit after an
554/// escape from being read as part of it.
555fn 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
570/// What one register is called, at that width, without the sigil.
571///
572/// The width is a general purpose register's business and nothing else's on this machine, since
573/// every other class here has one name per register, which is the name the register file gives.
574fn 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    /// A target of that object format, which is what decides how a symbol is spelled.
594    fn target(os: Os) -> TargetInfo {
595        TargetInfo::new(Triple::new(Arch::X86_64, os, Env::Gnu))
596    }
597
598    /// One function of one block, with those instructions in it, written out.
599    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    /// Those variables, written out for that object format.
616    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    /// The same, with every variable given a section of its own.
631    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    /// Two functions of those names, written out with each of them given a section of its own.
640    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    /// A four byte variable of that name, in that section, holding that image.
655    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    /// The instruction lines of that text, without the directives or the labels.
668    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        // The source before the destination, which is the reverse of the operand vector, and the
687        // first source not written at all, because it is the destination.
688        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        // Two instructions, the comparison at the width it was asked for and the set at the width
703        // a set is, which is the case that says why a width is a fact about an argument.
704        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        // The boundary is a power here and a count of bytes in the machine IR, because the
725        // assembler reads the one and a program writes the other. The fill is the one byte that
726        // does nothing, so a jump that lands in the padding still arrives. A directive rather than
727        // an instruction, which is why it is looked for in the whole text and not in the body.
728        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        // The first line of every function this compiler protects. No base and no index, because
760        // where the block begins is something only the machine knows, and the segment written in
761        // front of the constant rather than behind it, which is what an assembler reads.
762        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        // The only instruction this compiler writes that has a number and an address and no
776        // register of its own. An inclusive or of zero, so the byte it writes is the byte that was
777        // there, which is what makes it safe on a page nothing has been put in yet.
778        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        // The four bytes a symbol would leave, holding a distance the assembler works out for
810        // itself rather than one a relocation asks the linker for, since both ends of it are in
811        // the section being written.
812        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        // The same instruction and the same four bytes as the one above. What is different is
827        // which relocation those four bytes take, and the suffix on the name is the only place
828        // the assembler is told which.
829        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        // The third instruction that is the same instruction as the two above it. What comes back
844        // this time is not an address at all: it is how far into a thread's own block the variable
845        // sits, and what makes it an address is the addition that follows it.
846        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        // The underscore, which is the difference that would fail to link against every library
895        // on an Apple machine rather than merely looking odd.
896        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        // Still a symbol, and still at the alignment a function gets, because a local name is one
975        // the linker keeps and does not let another file reach.
976        assert!(text.contains("\nhidden:\n"), "{text}");
977        assert!(text.contains("\t.type\thidden, @function\n"), "{text}");
978        // What two files each defining their own `static helper` come down to.
979        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    /// The whole of what an assembler is told about one, and none of what it works out itself:
1003    /// the type and the size of the new name come from the old one, so they are not written
1004    /// again. gcc 16 writes exactly these two lines for the same input.
1005    #[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        // A local one is a name no directive announces, which is still an entry in the symbol
1042        // table and is what a `static` alias comes down to.
1043        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        // Four bytes of image and not sixteen, since three more names for one variable are three
1047        // more names and not three more variables.
1048        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        // The number at the width it is, rather than the four bytes it is made of, because a
1062        // listing is a thing to read and the bytes are the object's business.
1063        assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
1064        assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
1065    }
1066
1067    /// The flag and the type are the whole of what makes it thread-local in a listing, and they
1068    /// are what gcc 16.2.0 writes for `_Thread_local int counter = 42;`.
1069    #[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    /// The other half of the pair, which is `.bss` to the one above's `.data`.
1085    #[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        // The whole of what `static` at file scope means, and the one thing a reader would not
1104        // notice missing until two files each defined their own and the linker took one.
1105        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    /// The listing half of `-ffunction-sections`, which is the flag that makes `--gc-sections` able
1116    /// to drop anything: a linker can leave out a section nothing reaches and cannot leave out half
1117    /// of one.
1118    ///
1119    /// The empty `.text` at the top stays. It is what the file opens with either way, gcc 16 writes
1120    /// one under the flag too, and a section with nothing in it costs a header and confuses nobody.
1121    #[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        // In front of the alignment and the name rather than after them, since the padding belongs
1128        // to the section the function is in and a label in the wrong section is a wrong address.
1129        let opened = text.find(".section\t.text.first").expect("a section");
1130        assert!(opened < text.find("\nfirst:\n").expect("a label"), "{text}");
1131        // And one text section when nothing asked, which is the default.
1132        let plain = write(|_, _| {});
1133        assert!(!plain.contains(".text."), "{plain}");
1134    }
1135
1136    /// Mach-O takes the flag and writes what it wrote before, because every Mach-O object ends
1137    /// with `.subsections_via_symbols` and so already tells the linker it may split a section at
1138    /// each symbol and drop the parts nothing reaches. Clang does the same on an Apple target.
1139    #[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    /// The listing half of `-fdata-sections`, where the name of the section is the name of the one
1149    /// it came out of with the variable's name after it. That is what gcc writes, and the part in
1150    /// front of the dot is what a linker script and `--gc-sections` both match on.
1151    #[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        // Everything else about the variable is what it was: splitting moves which section header
1163        // the name is in and must not change the image, the size or who can see it.
1164        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        // And the flag reaches the data without reaching the code, since gcc has two flags and a
1168        // build that asked for one of them measured something.
1169        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        // Mach-O has no way to put bytes in its zero filled section, so a variable that goes
1179        // there is asked for by size the way a tentative definition is on every format.
1180        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        // Three octal digits every time, so that the digit after an escape is not read as part
1191        // of it, and the quote and the backslash escaped so the string ends where it should.
1192        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}