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//! An AArch64 file is written by the same walk, since sections, labels, unwind rows and variables
8//! are spelled the same way for both machines. Only the instruction is handed to [`crate::a64`].
9//!
10//! So there is almost nothing about x86-64 in this file. What an opcode is called, how many
11//! instructions it really is, which operand each of them is given and how wide each of those is
12//! written are all read out of the target. What is here is the syntax: a register carries a `%`,
13//! an immediate carries a `$`, an address is a displacement in front of a parenthesised base and
14//! index, and the source is written before the destination.
15//!
16//! Intel syntax, which section 11.1 requires as an input and which `-masm=intel` will ask for as
17//! an output, is the other order, no sigils and a different spelling of an address. It is a
18//! second walk over the same description rather than a second description, and it is not written
19//! yet.
20//!
21//! # What a block is
22//!
23//! A label, and then the instructions in it. Where a block goes is on the block rather than on
24//! its terminator, so a jump has already been made into an instruction by the block layout by
25//! the time anything gets here: what is left is to give each block a name, and the name is local
26//! so that it leaves no symbol behind for a debugger to show as if it were a function.
27//!
28//! # What the unwinder is told
29//!
30//! Every ELF function is wrapped in `.cfi_startproc` and `.cfi_endproc`, including the ones with
31//! no rows in them. An unwinder that lands on an address with no record covering it has to give
32//! up, so a leaf that never moves the stack pointer still needs a record: the one the CIE hands
33//! it, which says the frame ends at `rsp+8` and the return address is the word below that, is
34//! already the right answer for such a function and the empty record is how it asks for it.
35//!
36//! A row written after the last instruction of the last block is dropped. It would describe an
37//! address at or past the end of the function, which is outside what the record covers, and the
38//! usual thing to find there is an epilogue putting back a state nothing is going to read.
39//!
40//! # What is not written
41//!
42//! An opcode that is not an instruction is written as nothing. Three of them exist to hold a
43//! value in a register until something reads it, which is a fact the allocator needed and the
44//! machine does not, and by here it has been acted on: the register in the operand is the answer.
45
46use std::fmt::Write as _;
47
48use rucc_base::Interner;
49use rucc_mir::{Amode, Block, CfiOp, Func, Inst, Opcode, Operand, Reach, defs};
50use rucc_object::{Alias, FUNC_ALIGN, Output, Sections};
51use rucc_target::x86_64::{self, Arg, Width};
52use rucc_target::{PhysReg, RegClass, Segment, TargetInfo, aarch64};
53use rucc_tuple::Arch;
54
55use crate::Error;
56use crate::a64;
57use crate::data::{Globals, Piece, Variable};
58use crate::format::{Directives, binding, visibility};
59
60/// The prefix every x86-64 opcode carries in the machine IR.
61///
62/// An opcode is a name and a machine IR that holds two machines' instructions would otherwise
63/// have two `add_rr_32` in it. The description in `rucc-target` is indexed without it, because
64/// there it is already known which machine is being described.
65const PREFIX: &str = "x64.";
66
67/// Every function and every variable, as assembly text.
68///
69/// The functions first and the variables after them, which is the order every toolchain writes a
70/// file in and the order a person reading one expects. The second names come last, because a
71/// `.set` says nothing until the thing it names has been written down.
72///
73/// `unwind` is whether a function is described to an unwinder, which is
74/// `rucc_session::Options::unwinds` and is asked of the build rather than worked out here, so that
75/// this and the byte writer cannot answer it differently for one function.
76///
77/// `output` is whether each function and each variable is given a section of its own and what the
78/// file says it was built to have checked, which are the other things about this listing the caller
79/// decides: everything else is worked out from the functions and the target.
80///
81/// # Errors
82///
83/// [`Error::Machine`] for an architecture nothing here writes, and the two internal errors for a
84/// function that should not have got this far. See [`Error`].
85pub fn print(
86    funcs: &[Func],
87    globals: &Globals,
88    aliases: &[Alias],
89    names: &Interner,
90    target: &TargetInfo,
91    unwind: bool,
92    output: Output,
93) -> Result<String, Error> {
94    let Output { sections, property } = output;
95    let arch = target.tuple.arch();
96    if !matches!(arch, Arch::X86_64 | Arch::Aarch64) {
97        return Err(Error::Machine { triple: target.tuple.to_string() });
98    }
99    let directives = Directives::of(target.object_format);
100    let mut writer = Writer {
101        arch,
102        names,
103        directives,
104        // Nothing outside ELF reads one of these, and the directives for the other two formats are
105        // not the same ones, so a request for a table there is a request for nothing.
106        unwind: unwind && directives == Directives::Elf,
107        out: String::new(),
108        labels: Vec::new(),
109        sections,
110    };
111    writer.out.push_str(writer.directives.text());
112    writer.out.push('\n');
113    for func in funcs {
114        writer.func(func)?;
115    }
116    for var in &globals.vars {
117        writer.variable(var);
118    }
119    for alias in aliases {
120        writer.directives.alias(&mut writer.out, alias);
121    }
122    // Last, which is where gcc puts them. Each is a name and no bytes, so there is nothing to open
123    // a section for and nothing to close.
124    for name in &globals.weak {
125        writer.directives.absent(&mut writer.out, name);
126    }
127    writer.directives.end(&mut writer.out, property);
128    Ok(writer.out)
129}
130
131/// One template kept as text, filled in the way [`print()`] writes it into a listing.
132///
133/// For the byte writer, which reads a template on its own when the text allows it rather than
134/// sending the whole unit to the assembler. See `crate::bytes::template`. What goes into the holes
135/// is the same text either way, so a template cannot come out as one instruction in the listing and
136/// another in the object.
137pub(crate) fn template(
138    func: &Func,
139    block: Block,
140    inst: Inst,
141    names: &Interner,
142    directives: Directives,
143) -> Result<String, Error> {
144    let mut writer = Writer {
145        arch: Arch::X86_64,
146        names,
147        directives,
148        unwind: false,
149        out: String::new(),
150        labels: Vec::new(),
151        sections: Sections::default(),
152    };
153    writer.inst(func, block, inst, names.resolve(func.name))?;
154    Ok(writer.out)
155}
156
157/// A file being written out.
158struct Writer<'a> {
159    /// The machine the instructions are for, which is the one thing about a file that decides how
160    /// an instruction is spelled. See [`crate::a64`].
161    arch: Arch,
162    names: &'a Interner,
163    directives: Directives,
164    /// Whether each function is wrapped in an unwind record.
165    unwind: bool,
166    out: String,
167    /// The number each block is written as, indexed by its own, which is its place in the layout
168    /// rather than the order somebody happened to create the blocks in.
169    labels: Vec<u32>,
170    /// Whether each function and each variable is given a section of its own.
171    sections: Sections,
172}
173
174impl Writer<'_> {
175    /// One function: what the assembler is told about it, then its blocks.
176    fn func(&mut self, func: &Func) -> Result<(), Error> {
177        let name = self.names.resolve(func.name).to_owned();
178        self.number(func);
179        let binding = binding(func.binding);
180        let seen = visibility(func.visibility);
181        let align = func.align.unwrap_or(FUNC_ALIGN);
182        self.directives.code(&mut self.out, &name, self.sections);
183        // What has to be written between what the assembler is told about the function and the
184        // function's own label, which is nothing at all unless a patcher was promised room in
185        // front of the label. See `patch`.
186        let patch =
187            func.patch.map(|patch| (patch, format!("{}pfe_{name}", self.directives.local())));
188        let mut ahead = String::new();
189        if let Some((patch, label)) = &patch {
190            let back = if self.sections.functions {
191                format!("\t.section\t.text.{name}")
192            } else {
193                self.directives.text().to_owned()
194            };
195            self.directives.patchable(&mut ahead, label, &back);
196            if patch.before > 0 {
197                let _ = writeln!(ahead, "{label}:");
198                self.pad(&mut ahead, patch.pad, patch.before);
199            }
200        }
201        let fill = self.fill();
202        self.directives.open(&mut self.out, &name, align, fill, binding, seen, &ahead);
203        let unwind = self.unwind;
204        if unwind {
205            let _ = writeln!(self.out, "\t.cfi_startproc");
206        }
207        let end = func.cfi_end();
208        // How long each loop is, which the listing cannot say without the lengths of the
209        // instructions in it. The object writer's encoder is asked, and a function it cannot
210        // encode, which is one for another machine, has its loops left where they fall.
211        let loops = crate::bytes::loop_sizes(self.names, self.directives, func).unwrap_or_default();
212        for (index, block) in func.blocks().enumerate() {
213            let size = loops.get(block.index()).copied().unwrap_or(0);
214            if let Some(most) = crate::loop_room(size) {
215                let _ = writeln!(self.out, "\t.p2align\t6,,{most}");
216            }
217            let _ = writeln!(self.out, "{}{name}_{index}:", self.directives.local());
218            // And the name an image knows the block by, as a second label on the same address. The
219            // block's own label is written by this file and is a number, which is no good to a
220            // relocation in another section: what that names is a symbol, and the name here is the
221            // one the front end minted for it when it lowered the image.
222            if let Some(label) = func.block_name(block) {
223                let _ = writeln!(self.out, "{}:", self.names.resolve(label));
224            }
225            for inst in func.insts(block) {
226                // The other half of the room, which is named here rather than laid down here: the
227                // instructions it is made of are in the entry block like any others, and all that
228                // is missing is somewhere for the record to point. Named at the instruction rather
229                // than at the top of the block because a landing pad goes in front of it, and the
230                // room a patcher writes over does not include the pad.
231                if let Some((patch, label)) = &patch {
232                    if patch.before == 0 && patch.after == Some(inst) {
233                        let _ = writeln!(self.out, "{label}:");
234                    }
235                }
236                self.inst(func, block, inst, &name)?;
237                if unwind && Some(inst) != end {
238                    for op in func.cfi_after(inst) {
239                        self.cfi(op);
240                    }
241                }
242            }
243        }
244        self.tables(func, &name);
245        if unwind {
246            let _ = writeln!(self.out, "\t.cfi_endproc");
247        }
248        self.directives.close(&mut self.out, &name);
249        Ok(())
250    }
251
252    /// The instructions that do nothing which go in front of a function's own label.
253    ///
254    /// Written from the opcode rather than through the machinery every other instruction goes
255    /// through, because these are the only instructions in a finished function that are not in a
256    /// block and so are not instructions the function holds. The opcode is one with no operands,
257    /// which is what makes writing the mnemonic and nothing else the whole of it.
258    fn pad(&self, out: &mut String, pad: Opcode, count: u32) {
259        let spelled = self.names.resolve(pad.name());
260        let opcode = spelled.strip_prefix(self.prefix()).unwrap_or(spelled);
261        for _ in 0..count {
262            let _ = writeln!(out, "\t{opcode}");
263        }
264    }
265
266    /// One row of the unwind table, as the directive an assembler reads it as.
267    ///
268    /// The registers are written as numbers rather than as names, which is what gcc writes and
269    /// what avoids a second spelling of a register that could disagree with the first. They are
270    /// DWARF's numbers, which are not the machine's, and the one place the mapping lives is the
271    /// calling convention the prologue read it out of.
272    fn cfi(&mut self, op: CfiOp) {
273        let _ = match op {
274            CfiOp::DefCfa { reg, offset } => {
275                writeln!(self.out, "\t.cfi_def_cfa {reg}, {offset}")
276            }
277            CfiOp::DefCfaOffset(offset) => writeln!(self.out, "\t.cfi_def_cfa_offset {offset}"),
278            CfiOp::DefCfaRegister(reg) => writeln!(self.out, "\t.cfi_def_cfa_register {reg}"),
279            CfiOp::Offset { reg, offset } => writeln!(self.out, "\t.cfi_offset {reg}, {offset}"),
280            CfiOp::Restore(reg) => writeln!(self.out, "\t.cfi_restore {reg}"),
281            CfiOp::RememberState => writeln!(self.out, "\t.cfi_remember_state"),
282            CfiOp::RestoreState => writeln!(self.out, "\t.cfi_restore_state"),
283        };
284    }
285
286    /// One variable: what the assembler is told about it, then its image.
287    fn variable(&mut self, var: &Variable) {
288        if !self.directives.variable(&mut self.out, var, self.sections) {
289            return;
290        }
291        for piece in &var.pieces {
292            self.piece(piece);
293        }
294        self.directives.close(&mut self.out, &var.name);
295        self.directives.descriptor(&mut self.out, var);
296    }
297
298    /// One piece of an image, as the directive that says it.
299    ///
300    /// A number is written at the width it is rather than as the bytes it is made of, because the
301    /// point of a listing is to be read and `.long 258` is what a person wrote. The bytes are the
302    /// same either way, which is what [`crate::data`] is for.
303    fn piece(&mut self, piece: &Piece) {
304        match piece {
305            // `.space` rather than `.zero`, which every assembler also takes, because the
306            // directive set `spec/11-asm-objects-debug.md` says this compiler's own assembler
307            // reads has the one and not the other in it.
308            Piece::Zero(bytes) => {
309                let _ = writeln!(self.out, "\t.space\t{bytes}");
310            }
311            Piece::Bytes(bytes) => {
312                let _ = writeln!(self.out, "\t.ascii\t\"{}\"", escape(bytes));
313            }
314            Piece::Scalar(bytes) => match width(bytes.len()) {
315                Some(directive) => {
316                    let mut value = [0u8; 16];
317                    value[..bytes.len()].copy_from_slice(bytes);
318                    let _ = writeln!(self.out, "\t{directive}\t{}", u128::from_le_bytes(value));
319                }
320                // A width no directive names, which on this machine is the eighty bit float and
321                // nothing else. Its bytes are what it is.
322                None => {
323                    let list = bytes.iter().map(u8::to_string).collect::<Vec<_>>().join(", ");
324                    let _ = writeln!(self.out, "\t.byte\t{list}");
325                }
326            },
327            // Four and eight are the only widths that reach here, because the walk that built
328            // this refused every other one rather than leave the two halves to disagree.
329            // Written the way the template that asked for it wrote it, which is the one spelling
330            // an assembler reading this back would give the same relocation to.
331            Piece::Away { symbol, addend } => {
332                let name = format!("{}{symbol}", self.directives.symbol());
333                match addend {
334                    0 => {
335                        let _ = writeln!(self.out, "\t.long\t{name} - .");
336                    }
337                    _ => {
338                        let sign = if *addend < 0 { '-' } else { '+' };
339                        let _ = writeln!(self.out, "\t.long\t{name}{sign}{} - .", addend.abs());
340                    }
341                }
342            }
343            Piece::Addr { symbol, addend, bytes } => {
344                let directive = if *bytes == 8 { ".quad" } else { ".long" };
345                let name = format!("{}{symbol}", self.directives.symbol());
346                match addend {
347                    0 => {
348                        let _ = writeln!(self.out, "\t{directive}\t{name}");
349                    }
350                    _ => {
351                        let sign = if *addend < 0 { '-' } else { '+' };
352                        let _ = writeln!(self.out, "\t{directive}\t{name}{sign}{}", addend.abs());
353                    }
354                }
355            }
356            // Spelled the way a jump table's cells are, which is what gas reads as a number it
357            // works out itself when both labels are in one section.
358            Piece::Apart { to, from, addend, bytes } => {
359                let directive = width(usize::from(*bytes)).unwrap_or(".long");
360                let prefix = self.directives.symbol();
361                let _ = write!(self.out, "\t{directive}\t{prefix}{to}-{prefix}{from}");
362                let _ = match addend.signum() {
363                    1 => writeln!(self.out, "+{addend}"),
364                    -1 => writeln!(self.out, "-{}", addend.unsigned_abs()),
365                    _ => writeln!(self.out),
366                };
367            }
368        }
369    }
370
371    /// Gives every block the number its label carries.
372    fn number(&mut self, func: &Func) {
373        self.labels.clear();
374        self.labels.resize(func.block_count(), u32::MAX);
375        for (index, block) in func.blocks().enumerate() {
376            self.labels[block.index()] = u32::try_from(index).expect("a block number");
377        }
378    }
379
380    /// One instruction of the machine IR, as however many instructions of the machine it is.
381    fn inst(
382        &mut self,
383        func: &Func,
384        block: Block,
385        inst: Inst,
386        func_name: &str,
387    ) -> Result<(), Error> {
388        if self.arch == Arch::Aarch64 {
389            let spelling = match self.directives {
390                Directives::MachO => aarch64::Spelling::Apple,
391                Directives::Elf | Directives::Coff => aarch64::Spelling::Gnu,
392            };
393            let at = a64::Context {
394                names: self.names,
395                symbol: self.directives.symbol(),
396                spelling,
397                func_name,
398            };
399            let mut line = String::new();
400            let label = |to| self.label(func_name, to);
401            let table = |at: u32| self.table(func_name, at as usize);
402            a64::inst(&mut line, &at, func, block, inst, label, table)?;
403            self.out.push_str(&line);
404            return Ok(());
405        }
406        let data = func[inst];
407        let spelled = self.names.resolve(data.opcode.name());
408        let opcode = spelled.strip_prefix(PREFIX).unwrap_or(spelled);
409        // The one opcode that is not an instruction and is still written down. Everything below
410        // spells a mnemonic and its arguments, and this has neither: what it says is where the next
411        // instruction starts, which in a listing is the assembler's own directive. The fill byte is
412        // the one that does nothing, because a gap in the middle of a function is reached by falling
413        // into it rather than by jumping over it.
414        if opcode == x86_64::ALIGN {
415            let bytes = data.imm.map_or(0, |imm| func[imm].0);
416            let boundary = u32::try_from(bytes).ok().filter(|at| at.is_power_of_two());
417            let Some(boundary) = boundary else {
418                return Err(Error::Opcode {
419                    func: func_name.to_owned(),
420                    opcode: spelled.to_owned(),
421                });
422            };
423            let _ = writeln!(self.out, "\t.p2align\t{}, 0x90", boundary.trailing_zeros());
424            return Ok(());
425        }
426        // The other one, which is the bytes a template wrote out as themselves. They come back the
427        // way they went in, since the directive is what the program wrote and an assembler reading
428        // this listing has to get the same bytes out of it. One directive, because that is how many
429        // the instruction carries.
430        if opcode == x86_64::LITERAL {
431            let bytes: Vec<u8> =
432                data.imm.map(|imm| x86_64::unpacked(func[imm].0).collect()).unwrap_or_default();
433            if bytes.is_empty() {
434                return Err(Error::Opcode {
435                    func: func_name.to_owned(),
436                    opcode: spelled.to_owned(),
437                });
438            }
439            let written: Vec<String> = bytes.iter().map(|byte| format!("0x{byte:02x}")).collect();
440            let _ = writeln!(self.out, "\t.byte\t{}", written.join(", "));
441            return Ok(());
442        }
443        // A template kept as text, written back as the text with what it names filled in. Each of
444        // its lines goes down as a line of the listing, which is where gcc puts one too.
445        if opcode == x86_64::TEMPLATE {
446            let Some(text) = data.symbol else {
447                return Err(Error::Opcode {
448                    func: func_name.to_owned(),
449                    opcode: spelled.to_owned(),
450                });
451            };
452            let mem = match data.mem {
453                Some(mem) => self.amode(&func[data.operands], &func[mem], func_name, spelled)?,
454                None => String::new(),
455            };
456            // A register it names is the operand's, spelled at the width the hole says. Every one
457            // of them has a register by now, and one that has not is the same mistake it is in any
458            // other instruction.
459            let operands = &func[data.operands];
460            if operands.iter().any(|operand| operand.reg.phys().is_none()) {
461                return Err(Error::Virtual {
462                    func: func_name.to_owned(),
463                    opcode: spelled.to_owned(),
464                });
465            }
466            let prefix = self.directives.symbol();
467            let reg = |at: usize, width: char| {
468                let Some(operand) = operands.get(at) else { return String::from("?") };
469                let phys = operand.reg.phys().expect("every operand was checked above");
470                let named = match width {
471                    'b' => name_of(operand.class, phys, Width::Byte),
472                    'w' => name_of(operand.class, phys, Width::Word),
473                    'k' => name_of(operand.class, phys, Width::Long),
474                    'h' => x86_64::gpr_high(phys).unwrap_or("?"),
475                    _ => name_of(operand.class, phys, Width::Quad),
476                };
477                format!("%{named}")
478            };
479            let filled = x86_64::template_filled(
480                self.names.resolve(text),
481                &mem,
482                |name| format!("{prefix}{name}"),
483                reg,
484            );
485            for line in filled.lines() {
486                let _ = writeln!(self.out, "\t{}", line.trim_start());
487            }
488            return Ok(());
489        }
490        let Some(written) = x86_64::written(opcode) else {
491            return Err(Error::Opcode { func: func_name.to_owned(), opcode: spelled.to_owned() });
492        };
493        let operands = &func[data.operands];
494        for machine in written {
495            let mut args = Vec::with_capacity(machine.args.len());
496            for arg in machine.args {
497                args.push(match *arg {
498                    Arg::Reg(at, width) => {
499                        let operand = operands[usize::from(at)];
500                        self.reg(operand, width, func_name, spelled)?
501                    }
502                    // A whole vector register, whose name the register file holds outright. The
503                    // width is asked for anyway because the one thing that reads it is the general
504                    // purpose file, and a register in any other class has one name.
505                    Arg::Xmm(at) => {
506                        let operand = operands[usize::from(at)];
507                        self.reg(operand, Width::Quad, func_name, spelled)?
508                    }
509                    // The two halves of one word, which is the one instruction that names part of
510                    // a register rather than an amount of it. The low half is the byte the name
511                    // above would give it and the high half is the one only four registers have,
512                    // which is why the operand is fixed to one of the four where it is described.
513                    Arg::Low(at) => {
514                        let operand = operands[usize::from(at)];
515                        self.reg(operand, Width::Byte, func_name, spelled)?
516                    }
517                    Arg::High(at) => {
518                        let operand = operands[usize::from(at)];
519                        let Some(phys) = operand.reg.phys() else {
520                            return Err(Error::Virtual {
521                                func: func_name.to_owned(),
522                                opcode: spelled.to_owned(),
523                            });
524                        };
525                        format!("%{}", x86_64::gpr_high(phys).unwrap_or("?"))
526                    }
527                    Arg::Named(register) => format!("%{register}"),
528                    // A depth on the x87 stack rather than a register, which is why the number
529                    // comes from the table and not from an operand. The assembler writes the top
530                    // of the stack as `%st` on its own as well, and this writes `%st(0)` for it,
531                    // because one spelling for all eight is one thing fewer to know.
532                    Arg::Stack(depth) => format!("%st({depth})"),
533                    Arg::Lit(lane) => format!("${lane}"),
534                    // The first operand read, which is where a call puts the address it goes
535                    // through. Everything in front of it is a register the call writes.
536                    Arg::Through => {
537                        let operand = operands[defs(operands)];
538                        format!("*{}", self.reg(operand, Width::Quad, func_name, spelled)?)
539                    }
540                    Arg::Imm => match data.imm {
541                        Some(imm) => format!("${}", func[imm].0),
542                        None => "$0".to_owned(),
543                    },
544                    Arg::Mem => match data.mem {
545                        Some(mem) => self.amode(operands, &func[mem], func_name, spelled)?,
546                        None => "0".to_owned(),
547                    },
548                    Arg::Symbol => match data.symbol {
549                        Some(symbol) => {
550                            format!("{}{}", self.directives.symbol(), self.names.resolve(symbol))
551                        }
552                        None => "0".to_owned(),
553                    },
554                    // Where a conditional jump goes is the first arm, because the block layout
555                    // guarantees the second is the block laid out next and is fallen into. An
556                    // unconditional jump has one arm and it is the same one.
557                    Arg::Label => match func[block].succs.first() {
558                        Some(call) => self.label(func_name, call.block),
559                        None => "0".to_owned(),
560                    },
561                });
562            }
563            if args.is_empty() {
564                let _ = writeln!(self.out, "\t{}", machine.mnemonic);
565            } else {
566                let _ = writeln!(self.out, "\t{}\t{}", machine.mnemonic, args.join(", "));
567            }
568        }
569        Ok(())
570    }
571
572    /// One register operand, as much of it as the instruction reads or writes.
573    fn reg(
574        &self,
575        operand: Operand,
576        width: Width,
577        func_name: &str,
578        opcode: &str,
579    ) -> Result<String, Error> {
580        let Some(phys) = operand.reg.phys() else {
581            return Err(Error::Virtual { func: func_name.to_owned(), opcode: opcode.to_owned() });
582        };
583        Ok(format!("%{}", name_of(operand.class, phys, width)))
584    }
585
586    /// One address, which is a displacement and then whichever registers it names.
587    ///
588    /// A symbol with no base and no index is written relative to the instruction pointer, which
589    /// is how a global is reached in position independent code and is the only way this compiler
590    /// reaches one. A block is written the same way, and is the address a `&&label` produces.
591    fn amode(
592        &self,
593        operands: &[Operand],
594        amode: &Amode,
595        func_name: &str,
596        opcode: &str,
597    ) -> Result<String, Error> {
598        let mut out = String::new();
599        // In front of everything, which is where an assembler wants it: the segment says which
600        // storage the rest of the address is counted in, so `%fs:40` reads left to right.
601        match amode.segment {
602            Some(Segment::Fs) => out.push_str("%fs:"),
603            Some(Segment::Gs) => out.push_str("%gs:"),
604            None => {}
605        }
606        if let Some(symbol) = amode.symbol {
607            let _ = write!(out, "{}{}", self.directives.symbol(), self.names.resolve(symbol));
608            // The slot rather than the thing, which the assembler is told by the suffix and not by
609            // the instruction: the two are the same `movq` and differ only in what goes in the
610            // four bytes, so there is nowhere else to say it. The third one is a slot as well, and
611            // what it holds is an offset into a thread's own block rather than an address. On
612            // Mach-O the slot holds the address of the variable's descriptor instead, which is
613            // what the code calls through.
614            match amode.reach {
615                Reach::Itself => {}
616                Reach::Table => out.push_str("@GOTPCREL"),
617                Reach::Thread if self.directives == Directives::MachO => out.push_str("@TLVP"),
618                Reach::Thread => out.push_str("@GOTTPOFF"),
619            }
620            if amode.disp != 0 {
621                let sign = if amode.disp < 0 { '-' } else { '+' };
622                let _ = write!(out, "{sign}{}", i64::from(amode.disp).abs());
623            }
624        } else if let Some(block) = amode.block {
625            // A label of this function, which is written the way a symbol is and reached the way a
626            // symbol is, and is neither: what the assembler puts in the four bytes is a distance it
627            // works out itself, since both ends are in the section it is writing.
628            out.push_str(&self.label(func_name, block));
629            if amode.disp != 0 {
630                let sign = if amode.disp < 0 { '-' } else { '+' };
631                let _ = write!(out, "{sign}{}", i64::from(amode.disp).abs());
632            }
633        } else if let Some(table) = amode.table {
634            // A jump table of this function, which is a place in it the way a label is.
635            out.push_str(&self.table(func_name, table as usize));
636            if amode.disp != 0 {
637                let sign = if amode.disp < 0 { '-' } else { '+' };
638                let _ = write!(out, "{sign}{}", i64::from(amode.disp).abs());
639            }
640        } else if amode.disp != 0 || (amode.base.is_none() && amode.index.is_none()) {
641            // A mode that names no register at all is an absolute address, and zero is one of
642            // them, so the number is written even when it is zero and there is nothing else.
643            let _ = write!(out, "{}", amode.disp);
644        }
645        let base = amode.base.and_then(|at| operands.get(usize::from(at)));
646        let index = amode.index.and_then(|at| operands.get(usize::from(at)));
647        if base.is_some() || index.is_some() {
648            out.push('(');
649            if let Some(operand) = base {
650                out.push_str(&self.reg(*operand, Width::Quad, func_name, opcode)?);
651            }
652            if let Some(operand) = index {
653                let reg = self.reg(*operand, Width::Quad, func_name, opcode)?;
654                let _ = write!(out, ",{reg},{}", amode.scale);
655            }
656            out.push(')');
657        } else if amode.symbol.is_some() || amode.block.is_some() || amode.table.is_some() {
658            out.push_str("(%rip)");
659        }
660        Ok(out)
661    }
662
663    /// The jump tables, after the last instruction and inside the function, the way the encoder
664    /// lays them out. Each cell is the distance from the table to a block, which the assembler
665    /// works out itself since both ends are in this section. See `bytes::Assembler::tables`.
666    fn tables(&mut self, func: &Func, func_name: &str) {
667        if func.tables.is_empty() {
668            return;
669        }
670        let _ = match self.fill() {
671            Some(byte) => writeln!(self.out, "\t.p2align\t2, {byte:#x}"),
672            None => writeln!(self.out, "\t.p2align\t2"),
673        };
674        for (index, table) in func.tables.iter().enumerate() {
675            let label = self.table(func_name, index);
676            let _ = writeln!(self.out, "{label}:");
677            let block = func.block_of(table.jump).expect("a table read by a jump in no block");
678            let succs = &func[block].succs;
679            for &cell in &table.cells {
680                let to = self.label(func_name, succs[cell as usize].block);
681                let _ = writeln!(self.out, "\t.long\t{to}-{label}");
682            }
683        }
684    }
685
686    /// The label one jump table of one function carries. The `j` is what keeps it apart from a
687    /// block's label, which is a number after the same underscore.
688    fn table(&self, func_name: &str, index: usize) -> String {
689        format!("{}{func_name}_j{index}", self.directives.local())
690    }
691
692    /// What the machine IR puts in front of an opcode of this machine.
693    fn prefix(&self) -> &'static str {
694        if self.arch == Arch::Aarch64 { a64::PREFIX } else { PREFIX }
695    }
696
697    /// The byte padding inside code is made of, which is the one byte `nop` on x86-64. AArch64 has
698    /// no one byte instruction, so the assembler is left to pad with its own `nop`.
699    fn fill(&self) -> Option<u8> {
700        if self.arch == Arch::Aarch64 { None } else { Some(0x90) }
701    }
702
703    /// The label one block of one function carries.
704    fn label(&self, func_name: &str, block: Block) -> String {
705        match self.labels.get(block.index()).copied() {
706            Some(u32::MAX) | None => format!("{}{func_name}_?", self.directives.local()),
707            Some(number) => format!("{}{func_name}_{number}", self.directives.local()),
708        }
709    }
710}
711
712/// The directive that writes a number that many bytes wide, and `None` for a width none does.
713fn width(bytes: usize) -> Option<&'static str> {
714    match bytes {
715        1 => Some(".byte"),
716        2 => Some(".short"),
717        4 => Some(".long"),
718        8 => Some(".quad"),
719        _ => None,
720    }
721}
722
723/// Those bytes as the inside of a string an assembler reads back as the same bytes.
724///
725/// Everything outside printable ASCII is written as three octal digits rather than as itself,
726/// which is what keeps a string with a newline in it on one line and what stops a digit after an
727/// escape from being read as part of it.
728fn escape(bytes: &[u8]) -> String {
729    let mut out = String::with_capacity(bytes.len());
730    for byte in bytes {
731        match byte {
732            b'"' => out.push_str("\\\""),
733            b'\\' => out.push_str("\\\\"),
734            0x20..=0x7e => out.push(char::from(*byte)),
735            _ => {
736                let _ = write!(out, "\\{byte:03o}");
737            }
738        }
739    }
740    out
741}
742
743/// What one register is called, at that width, without the sigil.
744///
745/// The width is a general purpose register's business and nothing else's on this machine, since
746/// every other class here has one name per register, which is the name the register file gives.
747fn name_of(class: RegClass, reg: PhysReg, width: Width) -> &'static str {
748    let named = if class == x86_64::GPR {
749        x86_64::gpr_name(reg, width)
750    } else {
751        x86_64::REGS.name(class, reg)
752    };
753    named.unwrap_or("?")
754}
755
756#[cfg(test)]
757mod tests {
758    use super::*;
759
760    use rucc_base::Interner;
761    use rucc_mir::{Func, Mem, Operand, Reg};
762    use rucc_object::{Binding, Place, Visibility};
763    use rucc_target::x86_64::{GPR, RAX, RCX, RDX, RSP};
764    use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
765
766    /// A target of that object format, which is what decides how a symbol is spelled.
767    fn target(os: Os) -> TargetInfo {
768        TargetInfo::new(Triple::new(Arch::X86_64, os, Env::Gnu))
769    }
770
771    /// One function of one block, with those instructions in it, written out.
772    fn write(build: impl FnOnce(&mut Func, &mut Interner)) -> String {
773        let mut names = Interner::new();
774        let mut func = Func::new(names.intern("f"));
775        build(&mut func, &mut names);
776        print(
777            &[func],
778            &Globals::default(),
779            &[],
780            &names,
781            &target(Os::Linux),
782            true,
783            Output::default(),
784        )
785        .expect("a function that was allocated")
786    }
787
788    /// Those variables, written out for that object format.
789    fn data(vars: Vec<Variable>, os: Os) -> String {
790        let names = Interner::new();
791        print(
792            &[],
793            &Globals { vars, weak: Vec::new() },
794            &[],
795            &names,
796            &target(os),
797            true,
798            Output::default(),
799        )
800        .expect("a machine with a writer")
801    }
802
803    /// The same, with every variable given a section of its own.
804    fn split(vars: Vec<Variable>, os: Os) -> String {
805        let names = Interner::new();
806        let sections =
807            Output { sections: Sections { functions: false, data: true }, ..Output::default() };
808        print(&[], &Globals { vars, weak: Vec::new() }, &[], &names, &target(os), true, sections)
809            .expect("a machine with a writer")
810    }
811
812    /// Two functions of those names, written out with each of them given a section of its own.
813    fn split_code(first: &str, second: &str, os: Os) -> String {
814        let mut names = Interner::new();
815        let mut funcs = Vec::new();
816        for name in [first, second] {
817            let mut func = Func::new(names.intern(name));
818            func.create_block();
819            funcs.push(func);
820        }
821        let sections =
822            Output { sections: Sections { functions: true, data: false }, ..Output::default() };
823        print(&funcs, &Globals::default(), &[], &names, &target(os), true, sections)
824            .expect("a machine with a writer")
825    }
826
827    /// A four byte variable of that name, in that section, holding that image.
828    fn var(name: &str, place: Place, pieces: Vec<Piece>) -> Variable {
829        Variable {
830            name: name.to_owned(),
831            size: 4,
832            align: 4,
833            place,
834            binding: Binding::Global,
835            visibility: Visibility::Default,
836            pieces,
837        }
838    }
839
840    /// The instruction lines of that text, without the directives or the labels.
841    fn body(text: &str) -> Vec<&str> {
842        text.lines()
843            .filter(|line| line.starts_with('\t') && !line.trim_start().starts_with('.'))
844            .map(|line| line.trim_start())
845            .collect()
846    }
847
848    #[test]
849    fn an_instruction_is_written_the_way_the_target_says_it_is() {
850        let text = write(|func, names| {
851            let block = func.create_block();
852            let add = Opcode::new(names.intern("x64.add_rr_32"));
853            func.build(block, add)
854                .operand(Operand::write(Reg::physical(RAX), GPR))
855                .operand(Operand::read(Reg::physical(RAX), GPR))
856                .operand(Operand::read(Reg::physical(RCX), GPR))
857                .finish();
858        });
859        // The source before the destination, which is the reverse of the operand vector, and the
860        // first source not written at all, because it is the destination.
861        assert_eq!(body(&text), ["addl\t%ecx, %eax"]);
862    }
863
864    #[test]
865    fn an_opcode_the_machine_has_no_single_instruction_for_is_written_as_the_ones_it_has() {
866        let text = write(|func, names| {
867            let block = func.create_block();
868            let cmp = Opcode::new(names.intern("x64.cmp_set_l_64"));
869            func.build(block, cmp)
870                .operand(Operand::write(Reg::physical(RAX), GPR))
871                .operand(Operand::read(Reg::physical(RCX), GPR))
872                .operand(Operand::read(Reg::physical(RDX), GPR))
873                .finish();
874        });
875        // Two instructions, the comparison at the width it was asked for and the set at the width
876        // a set is, which is the case that says why a width is a fact about an argument.
877        assert_eq!(body(&text), ["cmpq\t%rdx, %rcx", "setl\t%al"]);
878    }
879
880    #[test]
881    fn an_opcode_that_is_not_an_instruction_is_written_as_nothing() {
882        let text = write(|func, names| {
883            let block = func.create_block();
884            let ret = Opcode::new(names.intern("x64.ret_val_32"));
885            func.build(block, ret).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
886        });
887        assert_eq!(body(&text), Vec::<&str>::new());
888    }
889
890    #[test]
891    fn an_alignment_is_written_as_the_directive_that_asks_for_it() {
892        let text = write(|func, names| {
893            let block = func.create_block();
894            let align = Opcode::new(names.intern("x64.align"));
895            func.build(block, align).imm(32).finish();
896        });
897        // The boundary is a power here and a count of bytes in the machine IR, because the
898        // assembler reads the one and a program writes the other. The fill is the one byte that
899        // does nothing, so a jump that lands in the padding still arrives. A directive rather than
900        // an instruction, which is why it is looked for in the whole text and not in the body.
901        assert!(text.contains("\n\t.p2align\t5, 0x90\n"), "{text}");
902        assert_eq!(body(&text), Vec::<&str>::new());
903    }
904
905    #[test]
906    fn an_address_is_a_displacement_and_then_the_registers_it_names() {
907        let text = write(|func, names| {
908            let block = func.create_block();
909            let lea = Opcode::new(names.intern("x64.lea_64"));
910            func.build(block, lea)
911                .operand(Operand::write(Reg::physical(RAX), GPR))
912                .mem(
913                    Mem::at(Operand::read(Reg::physical(RCX), GPR))
914                        .indexed(Operand::read(Reg::physical(RDX), GPR), 4)
915                        .plus(-16),
916                )
917                .finish();
918        });
919        assert_eq!(body(&text), ["leaq\t-16(%rcx,%rdx,4), %rax"]);
920    }
921
922    #[test]
923    fn an_address_in_a_thread_s_own_block_names_the_segment_and_no_register() {
924        let text = write(|func, names| {
925            let block = func.create_block();
926            let load = Opcode::new(names.intern("x64.mov_rm_64"));
927            func.build(block, load)
928                .operand(Operand::write(Reg::physical(RAX), GPR))
929                .mem(Mem::in_segment(Segment::Fs, 40))
930                .finish();
931        });
932        // The first line of every function this compiler protects. No base and no index, because
933        // where the block begins is something only the machine knows, and the segment written in
934        // front of the constant rather than behind it, which is what an assembler reads.
935        assert_eq!(body(&text), ["movq\t%fs:40, %rax"]);
936    }
937
938    #[test]
939    fn the_touch_a_probing_prologue_writes_is_an_immediate_and_then_an_address() {
940        let text = write(|func, names| {
941            let block = func.create_block();
942            let touch = Opcode::new(names.intern("x64.or_mi_8"));
943            func.build(block, touch)
944                .imm(0)
945                .mem(Mem::at(Operand::read(Reg::physical(RSP), GPR)))
946                .finish();
947        });
948        // The only instruction this compiler writes that has a number and an address and no
949        // register of its own. An inclusive or of zero, so the byte it writes is the byte that was
950        // there, which is what makes it safe on a page nothing has been put in yet.
951        assert_eq!(body(&text), ["orb\t$0, (%rsp)"]);
952    }
953
954    #[test]
955    fn an_address_with_nothing_but_a_symbol_in_it_is_relative_to_the_instruction_pointer() {
956        let text = write(|func, names| {
957            let block = func.create_block();
958            let load = Opcode::new(names.intern("x64.mov_rm_64"));
959            let global = names.intern("counter");
960            func.build(block, load)
961                .operand(Operand::write(Reg::physical(RAX), GPR))
962                .mem(Mem::of(global))
963                .finish();
964        });
965        assert_eq!(body(&text), ["movq\tcounter(%rip), %rax"]);
966    }
967
968    #[test]
969    fn an_address_with_a_label_in_it_is_the_label_and_is_relative_as_well() {
970        let text = write(|func, names| {
971            let head = func.create_block();
972            let there = func.create_block();
973            let lea = Opcode::new(names.intern("x64.lea_64"));
974            let jump = Opcode::new(names.intern("x64.jmp_reg"));
975            func.build(head, lea)
976                .operand(Operand::write(Reg::physical(RAX), GPR))
977                .mem(Mem::block(there))
978                .finish();
979            func.build(head, jump).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
980            func.build(there, Opcode::new(names.intern("x64.ret"))).finish();
981        });
982        // The four bytes a symbol would leave, holding a distance the assembler works out for
983        // itself rather than one a relocation asks the linker for, since both ends of it are in
984        // the section being written.
985        assert_eq!(body(&text), ["leaq\t.Lf_1(%rip), %rax", "jmp\t*%rax", "ret"]);
986    }
987
988    #[test]
989    fn a_jump_table_is_a_label_and_the_distance_to_each_block_from_it() {
990        let text = write(|func, names| {
991            let head = func.create_block();
992            let first = func.create_block();
993            let second = func.create_block();
994            let lea = Opcode::new(names.intern("x64.lea_64"));
995            let jmp = Opcode::new(names.intern("x64.jmp_reg"));
996            func.build(head, lea)
997                .operand(Operand::write(Reg::physical(RAX), GPR))
998                .mem(Mem::table(0))
999                .finish();
1000            let jump =
1001                func.build(head, jmp).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
1002            func.succs_mut(head).push(rucc_mir::BlockCall::to(first));
1003            func.succs_mut(head).push(rucc_mir::BlockCall::to(second));
1004            func.build(first, Opcode::new(names.intern("x64.ret"))).finish();
1005            func.build(second, Opcode::new(names.intern("x64.ret"))).finish();
1006            func.tables.push(rucc_mir::Table { jump, cells: vec![0, 1, 0] });
1007        });
1008        assert_eq!(body(&text), ["leaq\t.Lf_j0(%rip), %rax", "jmp\t*%rax", "ret", "ret"]);
1009        let table: Vec<&str> = text.lines().skip_while(|line| *line != ".Lf_j0:").take(4).collect();
1010        assert_eq!(
1011            table,
1012            [".Lf_j0:", "\t.long\t.Lf_1-.Lf_j0", "\t.long\t.Lf_2-.Lf_j0", "\t.long\t.Lf_1-.Lf_j0"],
1013            "{text}"
1014        );
1015    }
1016
1017    #[test]
1018    fn an_address_that_reads_the_offset_table_says_so_on_the_symbol() {
1019        let text = write(|func, names| {
1020            let block = func.create_block();
1021            let load = Opcode::new(names.intern("x64.mov_rm_64"));
1022            let away = names.intern("away");
1023            func.build(block, load)
1024                .operand(Operand::write(Reg::physical(RAX), GPR))
1025                .mem(Mem::got(away))
1026                .finish();
1027        });
1028        // The same instruction and the same four bytes as the one above. What is different is
1029        // which relocation those four bytes take, and the suffix on the name is the only place
1030        // the assembler is told which.
1031        assert_eq!(body(&text), ["movq\taway@GOTPCREL(%rip), %rax"]);
1032    }
1033
1034    #[test]
1035    fn an_address_that_reads_the_offset_of_a_thread_local_says_so_on_the_symbol_as_well() {
1036        let text = write(|func, names| {
1037            let block = func.create_block();
1038            let load = Opcode::new(names.intern("x64.mov_rm_64"));
1039            let away = names.intern("away");
1040            func.build(block, load)
1041                .operand(Operand::write(Reg::physical(RAX), GPR))
1042                .mem(Mem::thread(away))
1043                .finish();
1044        });
1045        // The third instruction that is the same instruction as the two above it. What comes back
1046        // this time is not an address at all: it is how far into a thread's own block the variable
1047        // sits, and what makes it an address is the addition that follows it.
1048        assert_eq!(body(&text), ["movq\taway@GOTTPOFF(%rip), %rax"]);
1049    }
1050
1051    #[test]
1052    fn a_jump_goes_to_the_label_of_the_block_the_first_arm_names() {
1053        let mut names = Interner::new();
1054        let mut func = Func::new(names.intern("f"));
1055        let first = func.create_block();
1056        let second = func.create_block();
1057        let jmp = Opcode::new(names.intern("x64.jmp"));
1058        func.build(first, jmp).finish();
1059        func.succs_mut(first).push(rucc_mir::BlockCall::to(second));
1060        let text = print(
1061            &[func],
1062            &Globals::default(),
1063            &[],
1064            &names,
1065            &target(Os::Linux),
1066            true,
1067            Output::default(),
1068        )
1069        .expect("a function of two blocks");
1070        assert!(text.contains("\tjmp\t.Lf_1\n"), "{text}");
1071        assert!(text.contains("\n.Lf_1:\n"), "{text}");
1072    }
1073
1074    #[test]
1075    fn a_symbol_is_spelled_the_way_the_object_format_spells_one() {
1076        let mut names = Interner::new();
1077        let mut func = Func::new(names.intern("f"));
1078        let block = func.create_block();
1079        let call = Opcode::new(names.intern("x64.call"));
1080        let callee = names.intern("puts");
1081        func.build(block, call).symbol(callee).finish();
1082
1083        let elf = print(
1084            std::slice::from_ref(&func),
1085            &Globals::default(),
1086            &[],
1087            &names,
1088            &target(Os::Linux),
1089            true,
1090            Output::default(),
1091        )
1092        .expect("elf");
1093        assert!(elf.contains("\tcall\tputs\n"), "{elf}");
1094        assert!(elf.contains("\n.Lf_0:\n"), "{elf}");
1095
1096        // The underscore, which is the difference that would fail to link against every library
1097        // on an Apple machine rather than merely looking odd.
1098        let macho = print(
1099            &[func],
1100            &Globals::default(),
1101            &[],
1102            &names,
1103            &target(Os::Darwin),
1104            true,
1105            Output::default(),
1106        )
1107        .expect("mach-o");
1108        assert!(macho.contains("\tcall\t_puts\n"), "{macho}");
1109        assert!(macho.contains("\n_f:\n"), "{macho}");
1110        assert!(macho.contains("\nLf_0:\n"), "{macho}");
1111    }
1112
1113    #[test]
1114    fn a_function_that_was_never_allocated_is_refused_rather_than_written_wrongly() {
1115        let mut names = Interner::new();
1116        let mut func = Func::new(names.intern("f"));
1117        let block = func.create_block();
1118        let vreg = func.new_vreg(GPR);
1119        let neg = Opcode::new(names.intern("x64.neg_r_32"));
1120        func.build(block, neg).operand(Operand::write(vreg, GPR)).finish();
1121        let error = print(
1122            &[func],
1123            &Globals::default(),
1124            &[],
1125            &names,
1126            &target(Os::Linux),
1127            true,
1128            Output::default(),
1129        )
1130        .expect_err("a virtual register");
1131        assert_eq!(
1132            error,
1133            Error::Virtual { func: "f".to_owned(), opcode: "x64.neg_r_32".to_owned() }
1134        );
1135    }
1136
1137    #[test]
1138    fn an_opcode_the_target_does_not_describe_is_refused() {
1139        let mut names = Interner::new();
1140        let mut func = Func::new(names.intern("f"));
1141        let block = func.create_block();
1142        let made_up = Opcode::new(names.intern("x64.frobnicate"));
1143        func.build(block, made_up).finish();
1144        let error = print(
1145            &[func],
1146            &Globals::default(),
1147            &[],
1148            &names,
1149            &target(Os::Linux),
1150            true,
1151            Output::default(),
1152        )
1153        .expect_err("no such instruction");
1154        assert_eq!(
1155            error,
1156            Error::Opcode { func: "f".to_owned(), opcode: "x64.frobnicate".to_owned() }
1157        );
1158    }
1159
1160    #[test]
1161    fn a_function_no_other_file_can_see_is_not_announced_to_the_linker() {
1162        let mut names = Interner::new();
1163        let mut hidden = Func::new(names.intern("hidden"));
1164        hidden.binding = rucc_mir::Binding::Local;
1165        hidden.create_block();
1166        let text = print(
1167            &[hidden],
1168            &Globals::default(),
1169            &[],
1170            &names,
1171            &target(Os::Linux),
1172            true,
1173            Output::default(),
1174        )
1175        .expect("elf");
1176        // Still a symbol, and still at the alignment a function gets, because a local name is one
1177        // the linker keeps and does not let another file reach.
1178        assert!(text.contains("\nhidden:\n"), "{text}");
1179        assert!(text.contains("\t.type\thidden, @function\n"), "{text}");
1180        // What two files each defining their own `static helper` come down to.
1181        assert!(!text.contains(".globl"), "{text}");
1182    }
1183
1184    #[test]
1185    fn a_function_that_may_lose_to_another_definition_is_written_weak() {
1186        let mut names = Interner::new();
1187        let mut shared = Func::new(names.intern("shared"));
1188        shared.binding = rucc_mir::Binding::Weak;
1189        shared.create_block();
1190        let text = print(
1191            &[shared],
1192            &Globals::default(),
1193            &[],
1194            &names,
1195            &target(Os::Linux),
1196            true,
1197            Output::default(),
1198        )
1199        .expect("elf");
1200        assert!(text.contains("\t.weak\tshared\n"), "{text}");
1201        assert!(!text.contains(".globl"), "{text}");
1202    }
1203
1204    /// The whole of what an assembler is told about one, and none of what it works out itself:
1205    /// the type and the size of the new name come from the old one, so they are not written
1206    /// again. gcc 16 writes exactly these two lines for the same input.
1207    #[test]
1208    fn a_second_name_is_a_binding_and_a_set_and_nothing_else() {
1209        let names = Interner::new();
1210        let aliases = [
1211            Alias {
1212                name: "b".to_owned(),
1213                target: "a".to_owned(),
1214                binding: Binding::Global,
1215                visibility: Visibility::Default,
1216            },
1217            Alias {
1218                name: "c".to_owned(),
1219                target: "a".to_owned(),
1220                binding: Binding::Weak,
1221                visibility: Visibility::Default,
1222            },
1223            Alias {
1224                name: "d".to_owned(),
1225                target: "a".to_owned(),
1226                binding: Binding::Local,
1227                visibility: Visibility::Default,
1228            },
1229        ];
1230        let vars = vec![var("a", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])])];
1231        let text = print(
1232            &[],
1233            &Globals { vars, weak: Vec::new() },
1234            &aliases,
1235            &names,
1236            &target(Os::Linux),
1237            true,
1238            Output::default(),
1239        )
1240        .expect("a machine with a writer");
1241        assert!(text.contains("\t.globl\tb\n\t.set\tb,a\n"), "{text}");
1242        assert!(text.contains("\t.weak\tc\n\t.set\tc,a\n"), "{text}");
1243        // A local one is a name no directive announces, which is still an entry in the symbol
1244        // table and is what a `static` alias comes down to.
1245        assert!(text.contains("\t.set\td,a\n"), "{text}");
1246        assert!(!text.contains("\t.type\tb"), "the type comes from what it points at: {text}");
1247        assert!(!text.contains("\t.size\tb"), "and so does the size: {text}");
1248        // Four bytes of image and not sixteen, since three more names for one variable are three
1249        // more names and not three more variables.
1250        assert_eq!(text.matches(".long\t1").count(), 1, "{text}");
1251    }
1252
1253    #[test]
1254    fn a_variable_is_a_section_a_name_and_the_bytes_between_them() {
1255        let text = data(
1256            vec![var("counter", Place::Written, vec![Piece::Scalar(vec![42, 0, 0, 0])])],
1257            Os::Linux,
1258        );
1259        assert!(text.contains("\t.data\n"), "{text}");
1260        assert!(text.contains("\t.globl\tcounter\n"), "{text}");
1261        assert!(text.contains("\t.p2align\t2\n"), "{text}");
1262        assert!(text.contains("\t.type\tcounter, @object\n"), "{text}");
1263        // The number at the width it is, rather than the four bytes it is made of, because a
1264        // listing is a thing to read and the bytes are the object's business.
1265        assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
1266        assert!(text.contains("\t.size\tcounter, .-counter\n"), "{text}");
1267    }
1268
1269    /// The flag and the type are the whole of what makes it thread-local in a listing, and they
1270    /// are what gcc 16.2.0 writes for `_Thread_local int counter = 42;`.
1271    #[test]
1272    fn a_thread_local_variable_is_a_section_with_the_flag_on_it_and_a_type_of_its_own() {
1273        let text = data(
1274            vec![var(
1275                "counter",
1276                Place::Thread { zero: false },
1277                vec![Piece::Scalar(vec![42, 0, 0, 0])],
1278            )],
1279            Os::Linux,
1280        );
1281        assert!(text.contains("\t.section\t.tdata,\"awT\",@progbits\n"), "{text}");
1282        assert!(text.contains("\t.type\tcounter, @tls_object\n"), "{text}");
1283        assert!(text.contains("\ncounter:\n\t.long\t42\n"), "{text}");
1284    }
1285
1286    /// The other half of the pair, which is `.bss` to the one above's `.data`.
1287    #[test]
1288    fn a_thread_local_variable_with_no_image_to_carry_goes_in_the_section_that_carries_none() {
1289        let text = data(
1290            vec![var("counter", Place::Thread { zero: true }, vec![Piece::Zero(4)])],
1291            Os::Linux,
1292        );
1293        assert!(text.contains("\t.section\t.tbss,\"awT\",@nobits\n"), "{text}");
1294        assert!(text.contains("\t.type\tcounter, @tls_object\n"), "{text}");
1295        assert!(text.contains("\ncounter:\n\t.space\t4\n"), "{text}");
1296    }
1297
1298    /// What clang writes for `__thread int counter = 42;` on either Apple architecture: the image
1299    /// under a name of its own, and the name the program uses on a descriptor that points at it.
1300    #[test]
1301    fn a_thread_local_variable_on_mach_o_is_an_image_and_a_descriptor() {
1302        let text = data(
1303            vec![var(
1304                "counter",
1305                Place::Thread { zero: false },
1306                vec![Piece::Scalar(vec![42, 0, 0, 0])],
1307            )],
1308            Os::Darwin,
1309        );
1310        let image = "\t.section\t__DATA,__thread_data,thread_local_regular\n\t.p2align\t2\n\
1311                     _counter$tlv$init:\n\t.long\t42\n";
1312        assert!(text.contains(image), "{text}");
1313        let descriptor = "\t.section\t__DATA,__thread_vars,thread_local_variables\n\
1314                          \t.globl\t_counter\n\t.p2align\t3\n_counter:\n\
1315                          \t.quad\t__tlv_bootstrap\n\t.quad\t0\n\t.quad\t_counter$tlv$init\n";
1316        assert!(text.contains(descriptor), "{text}");
1317        let zero = data(
1318            vec![var("counter", Place::Thread { zero: true }, vec![Piece::Zero(4)])],
1319            Os::Darwin,
1320        );
1321        assert!(zero.contains("\t.tbss\t_counter$tlv$init,4,2\n"), "{zero}");
1322        assert!(zero.contains(descriptor), "{zero}");
1323    }
1324
1325    #[test]
1326    fn a_variable_no_other_file_can_see_is_not_announced_to_the_linker() {
1327        let mut hidden = var("hidden", Place::Zero, vec![Piece::Zero(4)]);
1328        hidden.binding = Binding::Local;
1329        let text = data(vec![hidden], Os::Linux);
1330        assert!(text.contains("\t.bss\n"), "{text}");
1331        assert!(text.contains("\nhidden:\n\t.space\t4\n"), "{text}");
1332        // The whole of what `static` at file scope means, and the one thing a reader would not
1333        // notice missing until two files each defined their own and the linker took one.
1334        assert!(!text.contains(".globl"), "{text}");
1335    }
1336
1337    #[test]
1338    fn a_tentative_definition_is_a_request_rather_than_a_section_and_a_label() {
1339        let text = data(vec![var("x", Place::Merged, vec![Piece::Zero(4)])], Os::Linux);
1340        assert_eq!(text.lines().find(|line| line.contains(".comm")), Some("\t.comm\tx,4,4"));
1341        assert!(!text.contains("\nx:\n"), "nothing here says where it is: {text}");
1342    }
1343
1344    /// The listing half of `-ffunction-sections`, which is the flag that makes `--gc-sections` able
1345    /// to drop anything: a linker can leave out a section nothing reaches and cannot leave out half
1346    /// of one.
1347    ///
1348    /// The empty `.text` at the top stays. It is what the file opens with either way, gcc 16 writes
1349    /// one under the flag too, and a section with nothing in it costs a header and confuses nobody.
1350    #[test]
1351    fn every_function_gets_a_section_of_its_own_when_that_is_what_was_asked_for() {
1352        let text = split_code("first", "second", Os::Linux);
1353        assert!(text.starts_with("\t.text\n"), "{text}");
1354        assert!(text.contains("\t.section\t.text.first,\"ax\",@progbits\n"), "{text}");
1355        assert!(text.contains("\t.section\t.text.second,\"ax\",@progbits\n"), "{text}");
1356        // In front of the alignment and the name rather than after them, since the padding belongs
1357        // to the section the function is in and a label in the wrong section is a wrong address.
1358        let opened = text.find(".section\t.text.first").expect("a section");
1359        assert!(opened < text.find("\nfirst:\n").expect("a label"), "{text}");
1360        // And one text section when nothing asked, which is the default.
1361        let plain = write(|_, _| {});
1362        assert!(!plain.contains(".text."), "{plain}");
1363    }
1364
1365    /// Mach-O takes the flag and writes what it wrote before, because every Mach-O object ends
1366    /// with `.subsections_via_symbols` and so already tells the linker it may split a section at
1367    /// each symbol and drop the parts nothing reaches. Clang does the same on an Apple target.
1368    #[test]
1369    fn a_format_that_already_lets_the_linker_split_a_section_is_not_asked_to_split_it_again() {
1370        let text = split_code("first", "second", Os::Darwin);
1371        assert!(text.contains("\t.subsections_via_symbols\n"), "{text}");
1372        assert_eq!(text.matches(".section").count(), 1, "the one it opens with: {text}");
1373        let vars = vec![var("counter", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])])];
1374        assert_eq!(split(vars.clone(), Os::Darwin), data(vars, Os::Darwin));
1375    }
1376
1377    /// The listing half of `-fdata-sections`, where the name of the section is the name of the one
1378    /// it came out of with the variable's name after it. That is what gcc writes, and the part in
1379    /// front of the dot is what a linker script and `--gc-sections` both match on.
1380    #[test]
1381    fn every_variable_gets_a_section_named_after_it_when_that_is_what_was_asked_for() {
1382        let vars = vec![
1383            var("g", Place::Written, vec![Piece::Scalar(vec![1, 0, 0, 0])]),
1384            var("z", Place::Zero, vec![Piece::Zero(4)]),
1385            var("r", Place::ReadOnly, vec![Piece::Scalar(vec![3, 0, 0, 0])]),
1386        ];
1387        let text = split(vars.clone(), Os::Linux);
1388        assert!(text.contains("\t.section\t.data.g,\"aw\"\n\t.globl\tg\n"), "{text}");
1389        assert!(text.contains("\t.section\t.bss.z,\"aw\",@nobits\n"), "{text}");
1390        assert!(text.contains("\t.section\t.rodata.r,\"a\"\n"), "{text}");
1391        // Everything else about the variable is what it was: splitting moves which section header
1392        // the name is in and must not change the image, the size or who can see it.
1393        assert!(text.contains("\ng:\n\t.long\t1\n"), "{text}");
1394        assert!(text.contains("\t.size\tg, .-g\n"), "{text}");
1395        assert!(text.contains("\t.space\t4\n"), "{text}");
1396        // And the flag reaches the data without reaching the code, since gcc has two flags and a
1397        // build that asked for one of them measured something.
1398        assert!(!text.contains(".text."), "{text}");
1399        let plain = data(vars, Os::Linux);
1400        assert!(plain.contains("\t.data\n") && plain.contains("\t.bss\n"), "{plain}");
1401        assert!(!plain.contains(".data.g"), "{plain}");
1402    }
1403
1404    #[test]
1405    fn the_object_format_decides_how_a_variable_is_written_as_much_as_a_function() {
1406        let text = data(vec![var("x", Place::Zero, vec![Piece::Zero(4)])], Os::Darwin);
1407        // Mach-O has no way to put bytes in its zero filled section, so a variable that goes
1408        // there is asked for by size the way a tentative definition is on every format.
1409        // The directive is also the definition, so the binding goes above it or the variable is
1410        // one no other file can find.
1411        assert!(text.contains("\t.globl\t_x\n\t.zerofill\t__DATA,__bss,_x,4,2\n"), "{text}");
1412        let read_only = data(vec![var("x", Place::ReadOnly, vec![Piece::Zero(4)])], Os::Darwin);
1413        assert!(read_only.contains("\t.section\t__TEXT,__const\n"), "{read_only}");
1414        assert!(read_only.contains("\n_x:\n"), "the underscore, without which nothing links");
1415    }
1416
1417    #[test]
1418    fn a_run_of_bytes_is_written_so_that_it_reads_back_as_the_same_bytes() {
1419        let bytes = Piece::Bytes(b"a\"b\\\n\0\x801".to_vec());
1420        let text = data(vec![var("s", Place::ReadOnly, vec![bytes])], Os::Linux);
1421        // Three octal digits every time, so that the digit after an escape is not read as part
1422        // of it, and the quote and the backslash escaped so the string ends where it should.
1423        assert!(text.contains("\t.ascii\t\"a\\\"b\\\\\\012\\000\\2001\"\n"), "{text}");
1424    }
1425
1426    #[test]
1427    fn the_address_of_a_name_in_an_image_is_written_as_the_name() {
1428        let addr = Piece::Addr { symbol: "y".to_owned(), addend: 16, bytes: 8 };
1429        let text = data(vec![var("p", Place::Written, vec![addr])], Os::Linux);
1430        assert!(text.contains("\np:\n\t.quad\ty+16\n"), "{text}");
1431    }
1432
1433    #[test]
1434    fn a_distance_in_an_image_is_written_as_the_name_less_where_it_is() {
1435        let away = Piece::Away { symbol: "y".to_owned(), addend: 0 };
1436        let text = data(vec![var("d", Place::ReadOnly, vec![away])], Os::Linux);
1437        assert!(text.contains("\nd:\n\t.long\ty - .\n"), "{text}");
1438
1439        let away = Piece::Away { symbol: "y".to_owned(), addend: -3 };
1440        let text = data(vec![var("d", Place::ReadOnly, vec![away])], Os::Linux);
1441        assert!(text.contains("\nd:\n\t.long\ty-3 - .\n"), "{text}");
1442    }
1443
1444    #[test]
1445    fn a_distance_between_two_labels_is_written_as_one_less_the_other() {
1446        let piece = |addend, bytes| Piece::Apart {
1447            to: ".Llbl.1".to_owned(),
1448            from: ".Llbl.0".to_owned(),
1449            addend,
1450            bytes,
1451        };
1452        let text = data(vec![var("b", Place::ReadOnly, vec![piece(0, 4)])], Os::Linux);
1453        assert!(text.contains("\nb:\n\t.long\t.Llbl.1-.Llbl.0\n"), "{text}");
1454
1455        let text = data(vec![var("b", Place::ReadOnly, vec![piece(-2, 2)])], Os::Linux);
1456        assert!(text.contains("\nb:\n\t.short\t.Llbl.1-.Llbl.0-2\n"), "{text}");
1457    }
1458
1459    #[test]
1460    fn a_machine_with_no_writer_here_is_said_so_rather_than_written_as_x86_64() {
1461        let names = Interner::new();
1462        let riscv = TargetInfo::new(Triple::new(Arch::Riscv64, Os::Linux, Env::Gnu));
1463        let error = print(&[], &Globals::default(), &[], &names, &riscv, true, Output::default())
1464            .expect_err("no writer");
1465        assert!(matches!(error, Error::Machine { .. }), "{error:?}");
1466    }
1467
1468    /// One AArch64 function of one block, with those instructions in it, written out.
1469    fn write_a64(build: impl FnOnce(&mut Func, &mut Interner)) -> Result<String, Error> {
1470        let mut names = Interner::new();
1471        let mut func = Func::new(names.intern("f"));
1472        build(&mut func, &mut names);
1473        let target = TargetInfo::new(Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu));
1474        print(&[func], &Globals::default(), &[], &names, &target, true, Output::default())
1475    }
1476
1477    #[test]
1478    fn an_aarch64_instruction_is_written_the_way_its_own_table_says() {
1479        use rucc_target::aarch64::{self, x};
1480        let text = write_a64(|func, names| {
1481            let block = func.create_block();
1482            let add = Opcode::new(names.intern("a64.add_rr_32"));
1483            func.build(block, add)
1484                .operand(Operand::write(Reg::physical(x(0)), aarch64::GPR))
1485                .operand(Operand::read(Reg::physical(x(1)), aarch64::GPR))
1486                .operand(Operand::read(Reg::physical(x(2)), aarch64::GPR))
1487                .finish();
1488            let load = Opcode::new(names.intern("a64.ldr_64"));
1489            let base = Operand::read(Reg::physical(aarch64::SP), aarch64::GPR);
1490            func.build(block, load)
1491                .operand(Operand::write(Reg::physical(x(3)), aarch64::GPR))
1492                .mem(Mem::at(base).plus(16))
1493                .finish();
1494        })
1495        .expect("an allocated function");
1496        // Destination first, which is the order the operands are in already, and the stack
1497        // pointer as `sp` because 31 in a base is never the zero register.
1498        assert_eq!(body(&text), ["add w0, w1, w2", "ldr x3, [sp, #16]"]);
1499        // Padded by the assembler's own `nop`, since `0x90` is not an instruction here.
1500        assert!(text.contains("\t.p2align\t4\n"), "{text}");
1501        assert!(!text.contains("0x90"), "{text}");
1502    }
1503
1504    #[test]
1505    fn an_aarch64_register_left_virtual_is_refused() {
1506        let error = write_a64(|func, names| {
1507            let block = func.create_block();
1508            let mov = Opcode::new(names.intern("a64.mov_rr_64"));
1509            let class = aarch64::GPR;
1510            let v0 = func.new_vreg(class);
1511            let v1 = func.new_vreg(class);
1512            func.build(block, mov)
1513                .operand(Operand::write(v0, class))
1514                .operand(Operand::read(v1, class))
1515                .finish();
1516        })
1517        .expect_err("a register was never allocated");
1518        assert!(matches!(error, Error::Virtual { .. }), "{error:?}");
1519    }
1520
1521    #[test]
1522    fn an_aarch64_access_through_an_index_shifts_it_by_the_size() {
1523        use rucc_target::aarch64::{self, x};
1524        let text = write_a64(|func, names| {
1525            let block = func.create_block();
1526            let store = Opcode::new(names.intern("a64.str_32"));
1527            let base = Operand::read(Reg::physical(x(0)), aarch64::GPR);
1528            let index = Operand::read(Reg::physical(x(2)), aarch64::GPR);
1529            func.build(block, store)
1530                .operand(Operand::read(Reg::physical(x(3)), aarch64::GPR))
1531                .mem(Mem::at(base).indexed(index, 4))
1532                .finish();
1533            let load = Opcode::new(names.intern("a64.ldr_64"));
1534            func.build(block, load)
1535                .operand(Operand::write(Reg::physical(x(1)), aarch64::GPR))
1536                .mem(Mem::at(base).indexed(index, 1))
1537                .finish();
1538        })
1539        .expect("an allocated function");
1540        assert_eq!(body(&text), ["str w3, [x0, x2, lsl #2]", "ldr x1, [x0, x2]"]);
1541    }
1542
1543    #[test]
1544    fn an_aarch64_offset_the_encoder_refuses_is_not_written() {
1545        use rucc_target::aarch64::{self, x};
1546        let error = write_a64(|func, names| {
1547            let block = func.create_block();
1548            let load = Opcode::new(names.intern("a64.ldr_64"));
1549            let base = Operand::read(Reg::physical(x(0)), aarch64::GPR);
1550            func.build(block, load)
1551                .operand(Operand::write(Reg::physical(x(1)), aarch64::GPR))
1552                .mem(Mem::at(base).plus(1 << 20))
1553                .finish();
1554        })
1555        .expect_err("an offset no load can hold");
1556        assert!(matches!(error, Error::Encode { .. }), "{error:?}");
1557    }
1558
1559    #[test]
1560    fn an_opcode_aarch64_does_not_have_is_refused_rather_than_written_as_x86() {
1561        let error = write_a64(|func, names| {
1562            let block = func.create_block();
1563            func.build(block, Opcode::new(names.intern("x64.ret"))).finish();
1564        })
1565        .expect_err("not an AArch64 opcode");
1566        assert!(matches!(error, Error::Opcode { .. }), "{error:?}");
1567    }
1568
1569    #[test]
1570    fn the_head_of_a_loop_is_asked_to_stay_inside_one_line() {
1571        let mut names = Interner::new();
1572        let mut func = Func::new(names.intern("f"));
1573        func.create_block();
1574        let head = func.create_block();
1575        let jmp = Opcode::new(names.intern("x64.jmp"));
1576        func.build(head, jmp).finish();
1577        func.succs_mut(head).push(rucc_mir::BlockCall::to(head));
1578        func.heads = vec![head];
1579        let text = print(
1580            &[func],
1581            &Globals::default(),
1582            &[],
1583            &names,
1584            &target(Os::Linux),
1585            true,
1586            Output::default(),
1587        )
1588        .expect("a function with a loop in it");
1589        // The loop is the jump back to itself, five bytes, so it crosses a line only when it
1590        // starts in the last four bytes of one.
1591        let wanted = "\n.Lf_0:\n\t.p2align\t6,,4\n.Lf_1:\n";
1592        assert!(text.contains(wanted), "{text}");
1593    }
1594}