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, 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    writer.directives.end(&mut writer.out, property);
117    Ok(writer.out)
118}
119
120/// A file being written out.
121struct Writer<'a> {
122    names: &'a Interner,
123    directives: Directives,
124    /// Whether each function is wrapped in an unwind record.
125    unwind: bool,
126    out: String,
127    /// The number each block is written as, indexed by its own, which is its place in the layout
128    /// rather than the order somebody happened to create the blocks in.
129    labels: Vec<u32>,
130    /// Whether each function and each variable is given a section of its own.
131    sections: Sections,
132}
133
134impl Writer<'_> {
135    /// One function: what the assembler is told about it, then its blocks.
136    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        // What has to be written between what the assembler is told about the function and the
144        // function's own label, which is nothing at all unless a patcher was promised room in
145        // front of the label. See `patch`.
146        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                // The other half of the room, which is named here rather than laid down here: the
171                // instructions it is made of are in the entry block like any others, and all that
172                // is missing is somewhere for the record to point. Named at the instruction rather
173                // than at the top of the block because a landing pad goes in front of it, and the
174                // room a patcher writes over does not include the pad.
175                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    /// The instructions that do nothing which go in front of a function's own label.
196    ///
197    /// Written from the opcode rather than through the machinery every other instruction goes
198    /// through, because these are the only instructions in a finished function that are not in a
199    /// block and so are not instructions the function holds. The opcode is one with no operands,
200    /// which is what makes writing the mnemonic and nothing else the whole of it.
201    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    /// One row of the unwind table, as the directive an assembler reads it as.
210    ///
211    /// The registers are written as numbers rather than as names, which is what gcc writes and
212    /// what avoids a second spelling of a register that could disagree with the first. They are
213    /// DWARF's numbers, which are not the machine's, and the one place the mapping lives is the
214    /// calling convention the prologue read it out of.
215    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    /// One variable: what the assembler is told about it, then its image.
230    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    /// One piece of an image, as the directive that says it.
241    ///
242    /// A number is written at the width it is rather than as the bytes it is made of, because the
243    /// point of a listing is to be read and `.long 258` is what a person wrote. The bytes are the
244    /// same either way, which is what [`crate::data`] is for.
245    fn piece(&mut self, piece: &Piece) {
246        match piece {
247            // `.space` rather than `.zero`, which every assembler also takes, because the
248            // directive set `spec/11-asm-objects-debug.md` says this compiler's own assembler
249            // reads has the one and not the other in it.
250            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                // A width no directive names, which on this machine is the eighty bit float and
263                // nothing else. Its bytes are what it is.
264                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            // Four and eight are the only widths that reach here, because the walk that built
270            // this refused every other one rather than leave the two halves to disagree.
271            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    /// Gives every block the number its label carries.
288    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    /// One instruction of the machine IR, as however many instructions of the machine it is.
297    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                    // A whole vector register, whose name the register file holds outright. The
320                    // width is asked for anyway because the one thing that reads it is the general
321                    // purpose file, and a register in any other class has one name.
322                    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                    // A depth on the x87 stack rather than a register, which is why the number
328                    // comes from the table and not from an operand. The assembler writes the top
329                    // of the stack as `%st` on its own as well, and this writes `%st(0)` for it,
330                    // because one spelling for all eight is one thing fewer to know.
331                    Arg::Stack(depth) => format!("%st({depth})"),
332                    // The first operand read, which is where a call puts the address it goes
333                    // through. Everything in front of it is a register the call writes.
334                    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                    // Where a conditional jump goes is the first arm, because the block layout
353                    // guarantees the second is the block laid out next and is fallen into. An
354                    // unconditional jump has one arm and it is the same one.
355                    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    /// One register operand, as much of it as the instruction reads or writes.
371    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    /// One address, which is a displacement and then whichever registers it names.
385    ///
386    /// A symbol with no base and no index is written relative to the instruction pointer, which
387    /// is how a global is reached in position independent code and is the only way this compiler
388    /// reaches one.
389    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        // In front of everything, which is where an assembler wants it: the segment says which
398        // storage the rest of the address is counted in, so `%fs:40` reads left to right.
399        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            // The slot rather than the thing, which the assembler is told by the suffix and not by
407            // the instruction: the two are the same `movq` and differ only in what goes in the
408            // four bytes, so there is nowhere else to say it.
409            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            // A mode that names no register at all is an absolute address, and zero is one of
418            // them, so the number is written even when it is zero and there is nothing else.
419            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    /// The label one block of one function carries.
440    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
448/// The directive that writes a number that many bytes wide, and `None` for a width none does.
449fn 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
459/// Those bytes as the inside of a string an assembler reads back as the same bytes.
460///
461/// Everything outside printable ASCII is written as three octal digits rather than as itself,
462/// which is what keeps a string with a newline in it on one line and what stops a digit after an
463/// escape from being read as part of it.
464fn 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
479/// What one register is called, at that width, without the sigil.
480///
481/// The width is a general purpose register's business and nothing else's on this machine, since
482/// every other class here has one name per register, which is the name the register file gives.
483fn 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    /// A target of that object format, which is what decides how a symbol is spelled.
503    fn target(os: Os) -> TargetInfo {
504        TargetInfo::new(Triple::new(Arch::X86_64, os, Env::Gnu))
505    }
506
507    /// One function of one block, with those instructions in it, written out.
508    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    /// Those variables, written out for that object format.
525    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    /// The same, with every variable given a section of its own.
532    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    /// Two functions of those names, written out with each of them given a section of its own.
541    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    /// A four byte variable of that name, in that section, holding that image.
556    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    /// The instruction lines of that text, without the directives or the labels.
569    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        // The source before the destination, which is the reverse of the operand vector, and the
588        // first source not written at all, because it is the destination.
589        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        // Two instructions, the comparison at the width it was asked for and the set at the width
604        // a set is, which is the case that says why a width is a fact about an argument.
605        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        // The first line of every function this compiler protects. No base and no index, because
646        // where the block begins is something only the machine knows, and the segment written in
647        // front of the constant rather than behind it, which is what an assembler reads.
648        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        // The only instruction this compiler writes that has a number and an address and no
662        // register of its own. An inclusive or of zero, so the byte it writes is the byte that was
663        // there, which is what makes it safe on a page nothing has been put in yet.
664        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        // The same instruction and the same four bytes as the one above. What is different is
693        // which relocation those four bytes take, and the suffix on the name is the only place
694        // the assembler is told which.
695        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        // The underscore, which is the difference that would fail to link against every library
744        // on an Apple machine rather than merely looking odd.
745        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        // Still a symbol, and still at the alignment a function gets, because a local name is one
824        // the linker keeps and does not let another file reach.
825        assert!(text.contains("\nhidden:\n"), "{text}");
826        assert!(text.contains("\t.type\thidden, @function\n"), "{text}");
827        // What two files each defining their own `static helper` come down to.
828        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    /// The whole of what an assembler is told about one, and none of what it works out itself:
852    /// the type and the size of the new name come from the old one, so they are not written
853    /// again. gcc 16 writes exactly these two lines for the same input.
854    #[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        // A local one is a name no directive announces, which is still an entry in the symbol
891        // table and is what a `static` alias comes down to.
892        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        // Four bytes of image and not sixteen, since three more names for one variable are three
896        // more names and not three more variables.
897        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        // The number at the width it is, rather than the four bytes it is made of, because a
911        // listing is a thing to read and the bytes are the object's business.
912        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        // The whole of what `static` at file scope means, and the one thing a reader would not
924        // notice missing until two files each defined their own and the linker took one.
925        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    /// The listing half of `-ffunction-sections`, which is the flag that makes `--gc-sections` able
936    /// to drop anything: a linker can leave out a section nothing reaches and cannot leave out half
937    /// of one.
938    ///
939    /// The empty `.text` at the top stays. It is what the file opens with either way, gcc 16 writes
940    /// one under the flag too, and a section with nothing in it costs a header and confuses nobody.
941    #[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        // In front of the alignment and the name rather than after them, since the padding belongs
948        // to the section the function is in and a label in the wrong section is a wrong address.
949        let opened = text.find(".section\t.text.first").expect("a section");
950        assert!(opened < text.find("\nfirst:\n").expect("a label"), "{text}");
951        // And one text section when nothing asked, which is the default.
952        let plain = write(|_, _| {});
953        assert!(!plain.contains(".text."), "{plain}");
954    }
955
956    /// Mach-O takes the flag and writes what it wrote before, because every Mach-O object ends
957    /// with `.subsections_via_symbols` and so already tells the linker it may split a section at
958    /// each symbol and drop the parts nothing reaches. Clang does the same on an Apple target.
959    #[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    /// The listing half of `-fdata-sections`, where the name of the section is the name of the one
969    /// it came out of with the variable's name after it. That is what gcc writes, and the part in
970    /// front of the dot is what a linker script and `--gc-sections` both match on.
971    #[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        // Everything else about the variable is what it was: splitting moves which section header
983        // the name is in and must not change the image, the size or who can see it.
984        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        // And the flag reaches the data without reaching the code, since gcc has two flags and a
988        // build that asked for one of them measured something.
989        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        // Mach-O has no way to put bytes in its zero filled section, so a variable that goes
999        // there is asked for by size the way a tentative definition is on every format.
1000        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        // Three octal digits every time, so that the digit after an escape is not read as part
1011        // of it, and the quote and the backslash escaped so the string ends where it should.
1012        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}