Skip to main content

rucc_asm/
bytes.rs

1//! Machine functions as the bytes of a text section.
2//!
3//! Design: `spec/11-asm-objects-debug.md` section 11.1. The other end of [`crate::att`], and
4//! deliberately the same walk: an opcode is the list of instructions the target says it is, each
5//! instruction's arguments are drawn from the operands the target says they come from, and the
6//! only difference is that this hands each one to the encoder instead of writing its name. That
7//! is what section 11.1 means by one description rather than two, and it is why a mistake here
8//! cannot be a mistake about what an instruction is. It can only be a mistake about bytes.
9//!
10//! # What the encoder cannot know
11//!
12//! Where anything outside the instruction is. A jump carries the distance to its target and the
13//! target is a block that may not have been written yet, and a call carries the distance to a
14//! function that is not in this file at all. The encoder leaves four bytes for each and says
15//! where it left them, and this fills in the ones it can and records the ones it cannot.
16//!
17//! The ones it can are the jumps inside a function, since by the end of a function every block
18//! has a place. They are patched here and nothing downstream ever hears about them.
19//!
20//! The ones it cannot are the references to a symbol, which are a relocation: an offset into the
21//! section, the name of the thing wanted, and what the linker is being asked for. Choosing which
22//! relocation goes with which addressing mode is this layer's job rather than the object writer's,
23//! per section 11.3, because it is a fact about the instruction and not about the file format.
24//!
25//! # What is not decided here
26//!
27//! How long a jump is. Every one of them takes four bytes for its distance whether it needs them
28//! or not, which is correct and larger than it has to be. Shrinking the ones that fit in a byte is
29//! relaxation, an iterate-to-fixpoint pass over the whole function, and it is not written yet.
30//! Nothing here would have to change for it: it would run before this and settle the lengths.
31//!
32//! Alignment between functions, beyond starting each one on a sixteen byte boundary, which is what
33//! every x86-64 toolchain does and what the instruction fetcher is built around. The padding is
34//! written as single byte nops. A longer nop is fewer instructions to decode and the padding
35//! between two functions is never executed, so there is nothing to be gained by it.
36
37use rucc_base::Interner;
38use rucc_diag::Span;
39use rucc_mir::{Amode, Block, Func, Inst, Operand, Reach, defs};
40use rucc_target::x86_64::{self, Addr, Arg, RAX, Value, Width};
41use rucc_target::{PhysReg, TargetInfo};
42use rucc_tuple::Arch;
43
44use rucc_object::{Extent, FUNC_ALIGN, Marker, Patch, Reference, Reloc, Text};
45
46use crate::Error;
47use crate::format::{binding, visibility};
48use crate::unwind::{self, Rows};
49
50/// The prefix every x86-64 opcode carries in the machine IR.
51const PREFIX: &str = "x64.";
52
53/// The one byte instruction that does nothing, which is what the space in front of a function is.
54///
55/// Also what the room a patcher was promised is made of. The two are the same byte and not the same
56/// thing: the padding is space nothing reaches, and the room is space something jumps into once it
57/// has been written over. See `assemble`.
58const NOP: u8 = 0x90;
59
60/// Where one machine instruction ended up, and where in the source it came from.
61///
62/// The span rather than a file and a line, because this layer has no source map and no business
63/// acquiring one. Turning a span into a place is the driver's, which is also where the paths a
64/// `-ffile-prefix-map` rewrites are still paths.
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub struct Row {
67    /// How far into its own function the instruction begins.
68    pub at: usize,
69    /// What the machine IR said this instruction was for.
70    pub span: Span,
71}
72
73/// A text section and, when the build asked for it, where each instruction in it came from.
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct Assembled {
76    /// The instructions, and what the linker has to be told about them.
77    pub text: Text,
78    /// One list per function of [`Text::funcs`], in the same order, and empty throughout in a
79    /// build that asked for no debug information.
80    pub lines: Vec<Vec<Row>>,
81}
82
83/// Every function, as the bytes of a text section.
84///
85/// `unwind` is whether a function is described to an unwinder, which is
86/// `rucc_session::Options::unwinds` and is asked of the build rather than worked out here, so that
87/// this and the text writer cannot answer it differently for one function.
88///
89/// `lines` is whether to record where each instruction came from, which is
90/// `rucc_session::Options::debug_info` and is asked the same way and for the same reason. It is a
91/// question rather than something always answered because the rows are one per machine instruction
92/// and a build that is not writing debug information would carry them the length of the back end to
93/// throw them away.
94///
95/// # Errors
96///
97/// [`Error::Machine`] for an architecture nothing here encodes, and the rest for a function that
98/// should not have got this far. See [`Error`].
99///
100/// # Panics
101///
102/// Panics on a function that was promised room for a patcher and has none on either side of its
103/// own label, which is a prologue that recorded room it did not write.
104pub fn assemble(
105    funcs: &[Func],
106    names: &Interner,
107    target: &TargetInfo,
108    unwind: bool,
109    lines: bool,
110) -> Result<Assembled, Error> {
111    if target.tuple.arch() != Arch::X86_64 {
112        return Err(Error::Machine { triple: target.tuple.to_string() });
113    }
114    let mut text = Text::default();
115    let mut all = Vec::new();
116    // Where each function's frame rules landed, kept beside the extents rather than written into
117    // the section as they are found, because a record counts from the start of its function and the
118    // function's own length is not known until its last instruction has been encoded.
119    let mut rows = Vec::with_capacity(funcs.len());
120    for func in funcs {
121        // What this function asked for, which pads the space in front of it and, once every
122        // function has been through here, is what the whole section is aligned to. Both halves
123        // are needed: the offset inside the section is this padding and where the section itself
124        // lands is the alignment recorded on it. It goes on the extent as well, because under
125        // `-ffunction-sections` this function is a section of its own and the padding in front of
126        // it is gone, so this number is the only thing left saying what it wanted.
127        let align = func.align.unwrap_or(FUNC_ALIGN);
128        text.align = text.align.max(align);
129        let step = usize::try_from(align).unwrap_or(1).max(1);
130        while text.bytes.len() % step != 0 {
131            text.bytes.push(NOP);
132        }
133        // The half of the room a patcher was promised that is in front of the function's own
134        // label, laid down here because it is the one part of a finished function that is not in a
135        // block. What makes it the space in front of the function rather than the start of it is
136        // everything below: the symbol, the size and the record an unwinder reads all begin after
137        // it, which is what gcc does with the same flag and what a debugger showing a backtrace
138        // through a patched function needs.
139        //
140        // The byte is written rather than encoded because the room is counted in bytes and the
141        // instruction that fills it has no operands. `an_entry_promised_to_a_patcher_is_bytes_that
142        // _do_nothing_on_both_sides_of_the_symbol` is what holds it to the same byte the encoder
143        // writes for the half that is in a block.
144        let ahead = text.bytes.len();
145        if let Some(patch) = func.patch {
146            text.bytes.extend(std::iter::repeat_n(NOP, patch.before as usize));
147        }
148        let start = text.bytes.len();
149        let name = names.resolve(func.name).to_owned();
150        let mut assembler = Assembler {
151            names,
152            func,
153            name: &name,
154            text: &mut text,
155            blocks: Vec::new(),
156            jumps: Vec::new(),
157            rows: Vec::new(),
158            lines: Vec::new(),
159            wants: lines,
160            start,
161            room: None,
162        };
163        assembler.func()?;
164        let room = assembler.room;
165        rows.push(std::mem::take(&mut assembler.rows));
166        all.push(std::mem::take(&mut assembler.lines));
167        let len = text.bytes.len() - start;
168        // Where the record points is the front of the room, which is the half in front of the
169        // label in a function that has one and the first instruction of the other half otherwise.
170        // The two are not one offset because a landing pad can sit between the halves.
171        let patch = func.patch.map(|patch| {
172            let at = if patch.before > 0 {
173                ahead
174            } else {
175                room.expect("room that is neither in front of the label nor anywhere after it")
176            };
177            Patch { at, before: patch.before as usize }
178        });
179        text.funcs.push(Extent {
180            name,
181            start,
182            len,
183            align,
184            binding: binding(func.binding),
185            visibility: visibility(func.visibility),
186            patch,
187        });
188    }
189    // In whichever of the two shapes the target reads, which is what decides whether a prologue
190    // this cannot describe is a refusal or is nothing at all. See [`unwind::table`].
191    if unwind {
192        if let Some(conv) = target.call_regs {
193            text.unwind = unwind::table(&text.funcs, &rows, conv, target.object_format)?;
194        }
195    }
196    Ok(Assembled { text, lines: all })
197}
198
199/// A jump inside a function, waiting for the block it goes to to have a place.
200struct Jump {
201    /// Where the four bytes the distance goes in begin.
202    at: usize,
203    /// Where the instruction it belongs to ends, which is what the distance is counted from.
204    end: usize,
205    /// The block it goes to.
206    to: Block,
207    /// What is added to the distance, which is nothing for a jump and is the displacement for an
208    /// address that names a block and has one.
209    disp: i64,
210}
211
212/// One function being written out.
213struct Assembler<'a> {
214    names: &'a Interner,
215    func: &'a Func,
216    name: &'a str,
217    text: &'a mut Text,
218    /// Where each block starts, indexed by the block's own number, or [`usize::MAX`] for one that
219    /// is not in the layout.
220    blocks: Vec<usize>,
221    jumps: Vec<Jump>,
222    /// The frame rules, each with how far into this function the instruction that changed them
223    /// ended.
224    rows: Rows,
225    /// Where each machine instruction began and what it was for, in the order they were written.
226    ///
227    /// Empty in a build that asked for no debug information, which is what `wants` says.
228    lines: Vec<Row>,
229    /// Whether to fill `lines` in at all.
230    wants: bool,
231    /// Where this function starts in the section, which is what those distances are counted from.
232    start: usize,
233    /// Where the room a patcher was promised after the label began, which is where the instruction
234    /// [`rucc_mir::Patch::after`] names was encoded.
235    ///
236    /// [`None`] in a function that was promised none and in one whose room is all in front of the
237    /// label, which is the same answer to two different questions and is why the caller decides
238    /// which of them it asked. See `assemble`.
239    room: Option<usize>,
240}
241
242impl Assembler<'_> {
243    /// The blocks, and then the jumps between them once every block has a place.
244    fn func(&mut self) -> Result<(), Error> {
245        self.blocks = vec![usize::MAX; self.func.block_count()];
246        // The prologue, first, because nothing in it has a span of its own. The pushes, the frame
247        // and the moves that put the arguments where the body expects them came from no expression
248        // in the source, so without this the front of every function is the one part of it no row
249        // covers, and a program counter in there gets no answer at all rather than a slightly
250        // early one. Where the function was declared is what gcc says over those bytes.
251        if self.wants && !self.func.declared.is_dummy() {
252            self.lines.push(Row { at: 0, span: self.func.declared });
253        }
254        let end = self.func.cfi_end();
255        for block in self.func.blocks() {
256            self.blocks[block.index()] = self.text.bytes.len();
257            // And the name an image knows the block by, as a symbol at the same byte. The number
258            // the jumps above use is worked out here and stays here, because both ends of a jump
259            // are in this section. An image is in another one, so what it holds is a relocation
260            // and a relocation names a symbol, which is what this is.
261            if let Some(label) = self.func.block_name(block) {
262                let name = self.names.resolve(label).to_owned();
263                self.text.labels.push(Marker { name, at: self.text.bytes.len() });
264            }
265            for inst in self.func.insts(block) {
266                // Before it is encoded, because what is wanted is where it begins and after this
267                // it has already been written. A landing pad is in front of it in a function that
268                // has one, which is why the room is found this way rather than measured from the
269                // top of the function.
270                if self.func.patch.is_some_and(|patch| patch.after == Some(inst)) {
271                    self.room = Some(self.text.bytes.len());
272                }
273                // Where it begins rather than where it ends, which is the other way round from the
274                // frame rules below and for the same reason they are that way round: a debugger is
275                // asking what a program counter is in the middle of, and an unwinder is asking what
276                // the frame looked like at a return address.
277                if self.wants {
278                    let at = self.text.bytes.len() - self.start;
279                    self.lines.push(Row { at, span: self.func.span(inst) });
280                }
281                self.inst(block, inst)?;
282                if Some(inst) == end {
283                    continue;
284                }
285                // Where the instruction ended, because a row takes effect after the instruction
286                // that changed the answer and an unwinder is looking up a return address, which is
287                // the byte after a call rather than the call itself.
288                let at = self.text.bytes.len() - self.start;
289                self.rows.extend(self.func.cfi_after(inst).map(|op| (at, op)));
290            }
291        }
292        for jump in std::mem::take(&mut self.jumps) {
293            let to = self.blocks[jump.to.index()];
294            debug_assert_ne!(to, usize::MAX, "a jump to a block that was never laid out");
295            let distance = i64::try_from(to).expect("a section this size") + jump.disp
296                - i64::try_from(jump.end).expect("a section this size");
297            let distance = i32::try_from(distance)
298                .map_err(|_| Error::Distance { func: self.name.to_owned(), bytes: distance })?;
299            self.text.bytes[jump.at..jump.at + 4].copy_from_slice(&distance.to_le_bytes());
300        }
301        Ok(())
302    }
303
304    /// One instruction of the machine IR, as however many instructions of the machine it is.
305    fn inst(&mut self, block: Block, inst: Inst) -> Result<(), Error> {
306        let data = self.func[inst];
307        let spelled = self.names.resolve(data.opcode.name());
308        let opcode = spelled.strip_prefix(PREFIX).unwrap_or(spelled);
309        // The one opcode that is not an instruction. Where the listing writes the assembler's own
310        // directive this has to do what the assembler would have done, which is pad up to the
311        // boundary with the byte that does nothing, since the gap is reached by falling into it.
312        //
313        // The section has to be told as well. The padding puts the next instruction at a multiple of
314        // the boundary counted from the front of the section, and what makes that an address the
315        // program sees is the section itself landing on one, so the boundary goes on the section's
316        // alignment the way a function's own does.
317        if opcode == x86_64::ALIGN {
318            let bytes = data.imm.map_or(0, |imm| self.func[imm].0);
319            let boundary = u32::try_from(bytes).ok().filter(|at| at.is_power_of_two());
320            let Some(boundary) = boundary else {
321                return Err(Error::Opcode {
322                    func: self.name.to_owned(),
323                    opcode: spelled.to_owned(),
324                });
325            };
326            self.text.align = self.text.align.max(boundary);
327            let step = boundary as usize;
328            while self.text.bytes.len() % step != 0 {
329                self.text.bytes.push(NOP);
330            }
331            return Ok(());
332        }
333        // The other one, which is the bytes a template wrote out as themselves. There is nothing to
334        // encode: the program already said what the processor is to be handed, so they go down as
335        // they are.
336        if opcode == x86_64::LITERAL {
337            let Some(imm) = data.imm else {
338                return Err(Error::Opcode {
339                    func: self.name.to_owned(),
340                    opcode: spelled.to_owned(),
341                });
342            };
343            let before = self.text.bytes.len();
344            self.text.bytes.extend(x86_64::unpacked(self.func[imm].0));
345            if self.text.bytes.len() == before {
346                return Err(Error::Opcode {
347                    func: self.name.to_owned(),
348                    opcode: spelled.to_owned(),
349                });
350            }
351            return Ok(());
352        }
353        let Some(written) = x86_64::written(opcode) else {
354            return Err(Error::Opcode { func: self.name.to_owned(), opcode: spelled.to_owned() });
355        };
356        let operands = &self.func[data.operands];
357        for machine in written {
358            // What each argument turned out to be, and what the encoder has to be told about
359            // afterwards for the ones that name something it cannot see.
360            let mut values = Vec::with_capacity(machine.args.len());
361            let mut wanted = None;
362            // The other thing an address can name, which is a place in this same function and so is
363            // a distance nothing outside the file has to be told about.
364            let mut labelled = None;
365            for arg in machine.args {
366                values.push(match *arg {
367                    Arg::Reg(at, width) => {
368                        Value::Reg(self.phys(operands[usize::from(at)], spelled)?, width)
369                    }
370                    // The same thing in the other file, which the encoder has to be told apart
371                    // from the one above: which file a register is in is part of which instruction
372                    // it is, and the table it looks a row up in is what says so.
373                    Arg::Xmm(at) => Value::Xmm(self.phys(operands[usize::from(at)], spelled)?),
374                    // The only register named outright on this machine is the high half of the
375                    // first one, which an eight bit remainder comes back in.
376                    Arg::Named(_) => Value::High(RAX),
377                    // A depth on the x87 stack, which carries nothing across because there is
378                    // nothing to carry: the depth is in the opcode byte the mnemonic picks, so
379                    // what the encoder needs from here is that an argument was there at all.
380                    Arg::Stack(_) => Value::Stack,
381                    // The first operand read, which is where a call puts the address it goes
382                    // through. Everything in front of it is a register the call writes.
383                    Arg::Through => {
384                        Value::Reg(self.phys(operands[defs(operands)], spelled)?, Width::Quad)
385                    }
386                    Arg::Imm => Value::Imm(data.imm.map_or(0, |imm| self.func[imm].0)),
387                    Arg::Mem => {
388                        let amode = data.mem.map(|mem| self.func[mem]);
389                        let (addr, symbol) = self.addr(operands, amode.as_ref(), spelled)?;
390                        if let Some(symbol) = symbol {
391                            // A mode that reads the global offset table names the slot rather than
392                            // the thing, and the four bytes are the same four bytes either way, so
393                            // which relocation it is is the whole of the difference here.
394                            let kind = match amode.map_or(Reach::Itself, |mem| mem.reach) {
395                                Reach::Itself => Reference::Data,
396                                Reach::Table => Reference::Got,
397                                Reach::Thread => Reference::Thread,
398                            };
399                            wanted = Some((symbol, kind, i64::from(addr.disp)));
400                        }
401                        if let Some(block) = amode.and_then(|mem| mem.block) {
402                            labelled = Some((block, i64::from(addr.disp)));
403                        }
404                        Value::Mem(addr)
405                    }
406                    Arg::Symbol => {
407                        let symbol =
408                            data.symbol.map(|symbol| self.names.resolve(symbol).to_owned());
409                        if let Some(symbol) = symbol {
410                            wanted = Some((symbol, Reference::Call, 0));
411                        }
412                        Value::Dest
413                    }
414                    // Where a conditional jump goes is the first arm, because the block layout
415                    // guarantees the second is the block laid out next and is fallen into.
416                    Arg::Label => Value::Dest,
417                });
418            }
419
420            let holes =
421                x86_64::encode(machine.mnemonic, &values, &mut self.text.bytes).map_err(|why| {
422                    Error::Encode {
423                        func: self.name.to_owned(),
424                        opcode: spelled.to_owned(),
425                        why: why.to_string(),
426                    }
427                })?;
428            let end = self.text.bytes.len();
429
430            // A hole is either something outside the file, which is a relocation, or a block of
431            // this function, which is patched once every block has a place.
432            if let Some((symbol, kind, disp)) = wanted {
433                let at = match kind {
434                    Reference::Call => holes.dest,
435                    Reference::Data | Reference::Got | Reference::Thread => holes.rip,
436                    // An address written into an image rather than reached by an instruction, and
437                    // how far something is from the front of one, which is what a table of data
438                    // holds. Nothing above produces either, because every reference an instruction
439                    // makes is a distance from where the instruction ends.
440                    Reference::Address { .. } | Reference::Image | Reference::Away => {
441                        unreachable!("an instruction wanting an address")
442                    }
443                };
444                let at = at.expect("an instruction naming a symbol leaves room for the distance");
445                let addend = disp - i64::try_from(end - at).expect("an instruction this long");
446                // How many bytes of the instruction come after the four the linker writes over,
447                // which is what is left of the distance from the hole to the end of it. Already in
448                // the addend and written down again because COFF wants the two apart, and there is
449                // nowhere else it can be worked out: by the time a writer sees the relocation the
450                // instruction it is in is bytes like any others.
451                let after = u8::try_from(end - at - 4).expect("an instruction this long");
452                self.text.relocs.push(Reloc { at, symbol, kind, addend, after });
453            } else if let Some((to, disp)) = labelled {
454                // The address of a label, which is the four bytes an address counted from the
455                // instruction pointer leaves and is patched where a jump is patched rather than
456                // written out as a relocation, since both ends of it are in this function.
457                let at = holes.rip.expect("an address naming a label leaves room for the distance");
458                self.jumps.push(Jump { at, end, to, disp });
459            } else if let Some(at) = holes.dest {
460                match self.func[block].succs.first() {
461                    Some(call) => self.jumps.push(Jump { at, end, to: call.block, disp: 0 }),
462                    None => debug_assert!(false, "a jump out of a block with no arms"),
463                }
464            }
465        }
466        Ok(())
467    }
468
469    /// One address, with the operands it names resolved and the symbol it names handed back.
470    ///
471    /// A symbol with no base and no index is reached from the instruction pointer, which is how a
472    /// global is reached in position independent code and the only way this compiler reaches one.
473    /// The displacement is written into the instruction and counted again in the relocation's
474    /// addend, because a linker writes the whole four bytes from the addend and never reads what
475    /// was there. What is in the bytes is what the instruction meant before anything was linked,
476    /// which is what a person disassembling the object file would want to see.
477    fn addr(
478        &self,
479        operands: &[Operand],
480        amode: Option<&Amode>,
481        opcode: &str,
482    ) -> Result<(Addr, Option<String>), Error> {
483        let Some(amode) = amode else {
484            return Ok((Addr::default(), None));
485        };
486        let base = match amode.base {
487            Some(at) => Some(self.phys(operands[usize::from(at)], opcode)?),
488            None => None,
489        };
490        let index = match amode.index {
491            Some(at) => Some(self.phys(operands[usize::from(at)], opcode)?),
492            None => None,
493        };
494        let symbol = amode.symbol.map(|symbol| self.names.resolve(symbol).to_owned());
495        // A block is reached the same way and leaves the same four bytes. What is different is who
496        // fills them in, which is this file rather than the linker, and that is the caller's to
497        // sort out: what it needs from here is that the address was written that way at all.
498        let names = symbol.is_some() || amode.block.is_some();
499        let rip = names && base.is_none() && index.is_none();
500        let addr =
501            Addr { base, index, scale: amode.scale, disp: amode.disp, rip, segment: amode.segment };
502        Ok((addr, if rip { symbol } else { None }))
503    }
504
505    /// The real register one operand ended up in.
506    fn phys(&self, operand: Operand, opcode: &str) -> Result<PhysReg, Error> {
507        operand
508            .reg
509            .phys()
510            .ok_or_else(|| Error::Virtual { func: self.name.to_owned(), opcode: opcode.to_owned() })
511    }
512}
513
514#[cfg(test)]
515mod tests {
516    use super::*;
517
518    use rucc_base::Interner;
519    use rucc_mir::{BlockCall, Mem, Opcode, Reg};
520    use rucc_object::{Binding, Visibility};
521    use rucc_target::x86_64::{GPR, RAX, RCX, RDX};
522    use rucc_target::{Arch, Env, Os, Triple};
523
524    /// A linux x86-64 target, which is the one every case here is written for.
525    fn target() -> TargetInfo {
526        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
527    }
528
529    /// One function of one block, with those instructions in it, assembled.
530    fn write(build: impl FnOnce(&mut Func, &mut Interner)) -> Text {
531        let mut names = Interner::new();
532        let mut func = Func::new(names.intern("f"));
533        build(&mut func, &mut names);
534        assemble(&[func], &names, &target(), true, false)
535            .expect("a function that was allocated")
536            .text
537    }
538
539    /// Those bytes, as the hexadecimal a manual writes them in.
540    fn hex(bytes: &[u8]) -> String {
541        bytes.iter().map(|byte| format!("{byte:02x}")).collect::<Vec<_>>().join(" ")
542    }
543
544    /// An addition of two registers, which is the smallest instruction with operands there is.
545    fn add(func: &mut Func, names: &mut Interner) {
546        let block = func.create_block();
547        let add = Opcode::new(names.intern("x64.add_rr_32"));
548        func.build(block, add)
549            .operand(Operand::write(Reg::physical(RAX), GPR))
550            .operand(Operand::read(Reg::physical(RAX), GPR))
551            .operand(Operand::read(Reg::physical(RCX), GPR))
552            .finish();
553    }
554
555    #[test]
556    fn an_instruction_is_the_bytes_the_target_says_it_is() {
557        let text = write(add);
558        assert_eq!(hex(&text.bytes), "01 c8");
559        let f = Extent {
560            name: "f".to_owned(),
561            start: 0,
562            len: 2,
563            align: FUNC_ALIGN,
564            binding: Binding::Global,
565            visibility: Visibility::Default,
566            patch: None,
567        };
568        assert_eq!(text.funcs, [f]);
569        assert!(text.relocs.is_empty());
570    }
571
572    #[test]
573    fn an_opcode_the_machine_has_no_single_instruction_for_is_all_the_ones_it_has() {
574        let text = write(|func, names| {
575            let block = func.create_block();
576            let cmp = Opcode::new(names.intern("x64.cmp_set_l_64"));
577            func.build(block, cmp)
578                .operand(Operand::write(Reg::physical(RAX), GPR))
579                .operand(Operand::read(Reg::physical(RCX), GPR))
580                .operand(Operand::read(Reg::physical(RDX), GPR))
581                .finish();
582        });
583        // The comparison at the width it was asked for and then the set, which is the same two
584        // instructions the assembly path writes and is why one description rather than two.
585        assert_eq!(hex(&text.bytes), "48 39 d1 0f 9c c0");
586    }
587
588    #[test]
589    fn an_opcode_that_is_not_an_instruction_is_no_bytes_at_all() {
590        let text = write(|func, names| {
591            let block = func.create_block();
592            let ret = Opcode::new(names.intern("x64.ret_val_32"));
593            func.build(block, ret).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
594        });
595        assert!(text.bytes.is_empty(), "{:?}", text.bytes);
596    }
597
598    #[test]
599    fn an_alignment_is_the_bytes_between_where_it_is_and_the_boundary_it_asks_for() {
600        let text = write(|func, names| {
601            let block = func.create_block();
602            let add = Opcode::new(names.intern("x64.add_rr_32"));
603            let align = Opcode::new(names.intern("x64.align"));
604            let two = |func: &mut Func| {
605                func.build(block, add)
606                    .operand(Operand::write(Reg::physical(RAX), GPR))
607                    .operand(Operand::read(Reg::physical(RAX), GPR))
608                    .operand(Operand::read(Reg::physical(RCX), GPR))
609                    .finish();
610            };
611            two(func);
612            func.build(block, align).imm(8).finish();
613            two(func);
614        });
615        // Two bytes of addition, six of nothing, two more of addition. The padding is the one byte
616        // instruction that does nothing rather than a run of zeroes, because the processor may walk
617        // through it to get to what comes after, which is the whole reason a program asks.
618        assert_eq!(hex(&text.bytes), "01 c8 90 90 90 90 90 90 01 c8");
619        // The section has to be told as well. A function aligned to eight inside a section aligned
620        // to one is aligned to eight in its own reckoning and to nothing at all in the program's.
621        assert!(text.align >= 8, "{}", text.align);
622    }
623
624    /// The bytes a template wrote out itself, which go down as they are.
625    ///
626    /// `xgetbv` written as its three bytes, which is how every program that has one writes it,
627    /// between two instructions so that what is checked is that the bytes land where the program
628    /// put them and not just that they land.
629    #[test]
630    fn a_byte_out_of_a_template_is_that_byte_and_nothing_around_it() {
631        let text = write(|func, names| {
632            let block = func.create_block();
633            let add = Opcode::new(names.intern("x64.add_rr_32"));
634            let byte = Opcode::new(names.intern("x64.byte"));
635            let two = |func: &mut Func| {
636                func.build(block, add)
637                    .operand(Operand::write(Reg::physical(RAX), GPR))
638                    .operand(Operand::read(Reg::physical(RAX), GPR))
639                    .operand(Operand::read(Reg::physical(RCX), GPR))
640                    .finish();
641            };
642            two(func);
643            let bytes = x86_64::packed(&[0x0f, 0x01, 0xd0]).expect("three bytes fit");
644            func.build(block, byte).imm(bytes).finish();
645            two(func);
646        });
647        assert_eq!(hex(&text.bytes), "01 c8 0f 01 d0 01 c8");
648    }
649
650    #[test]
651    fn a_jump_inside_a_function_is_filled_in_rather_than_left_to_the_linker() {
652        let mut names = Interner::new();
653        let mut func = Func::new(names.intern("f"));
654        let first = func.create_block();
655        let second = func.create_block();
656        let add = Opcode::new(names.intern("x64.add_rr_32"));
657        func.build(first, add)
658            .operand(Operand::write(Reg::physical(RAX), GPR))
659            .operand(Operand::read(Reg::physical(RAX), GPR))
660            .operand(Operand::read(Reg::physical(RCX), GPR))
661            .finish();
662        let jmp = Opcode::new(names.intern("x64.jmp"));
663        func.build(second, jmp).finish();
664        func.succs_mut(second).push(BlockCall::to(first));
665
666        let text = assemble(&[func], &names, &target(), true, false).expect("two blocks").text;
667        // Two bytes of addition, then a jump back over itself and over them, which is seven bytes
668        // backwards because a jump counts from where it ends.
669        assert_eq!(hex(&text.bytes), "01 c8 e9 f9 ff ff ff");
670        assert!(text.relocs.is_empty(), "a jump inside a function is not the linker's business");
671    }
672
673    #[test]
674    fn the_address_of_a_label_is_filled_in_here_as_well() {
675        let mut names = Interner::new();
676        let mut func = Func::new(names.intern("f"));
677        let first = func.create_block();
678        let second = func.create_block();
679        let lea = Opcode::new(names.intern("x64.lea_64"));
680        func.build(first, lea)
681            .operand(Operand::write(Reg::physical(RAX), GPR))
682            .mem(Mem::block(second))
683            .finish();
684        let jmp = Opcode::new(names.intern("x64.jmp_reg"));
685        func.build(first, jmp).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
686        func.succs_mut(first).push(BlockCall::to(second));
687        func.build(second, Opcode::new(names.intern("x64.ret"))).finish();
688
689        let text = assemble(&[func], &names, &target(), true, false).expect("two blocks").text;
690        // Seven bytes of address, two of jump, and then the block. The distance is two, because
691        // the four bytes count from the end of the instruction that holds them and the jump is
692        // what is in between.
693        assert_eq!(hex(&text.bytes), "48 8d 05 02 00 00 00 ff e0 c3");
694        assert!(text.relocs.is_empty(), "a label of this function is not the linker's business");
695    }
696
697    #[test]
698    fn a_call_leaves_the_linker_the_name_of_what_it_calls() {
699        let mut names = Interner::new();
700        let mut func = Func::new(names.intern("f"));
701        let block = func.create_block();
702        let call = Opcode::new(names.intern("x64.call"));
703        let callee = names.intern("puts");
704        func.build(block, call).symbol(callee).finish();
705
706        let text = assemble(&[func], &names, &target(), true, false).expect("a call").text;
707        assert_eq!(hex(&text.bytes), "e8 00 00 00 00");
708        assert_eq!(
709            text.relocs,
710            [Reloc {
711                at: 1,
712                symbol: "puts".to_owned(),
713                kind: Reference::Call,
714                addend: -4,
715                after: 0
716            }]
717        );
718    }
719
720    #[test]
721    fn a_global_is_a_relocation_counted_from_the_end_of_the_instruction() {
722        let mut names = Interner::new();
723        let mut func = Func::new(names.intern("f"));
724        let block = func.create_block();
725        let load = Opcode::new(names.intern("x64.mov_rm_64"));
726        let global = names.intern("counter");
727        func.build(block, load)
728            .operand(Operand::write(Reg::physical(RAX), GPR))
729            .mem(Mem::of(global).plus(8))
730            .finish();
731
732        let text =
733            assemble(&[func], &names, &target(), true, false).expect("a load of a global").text;
734        assert_eq!(hex(&text.bytes), "48 8b 05 08 00 00 00");
735        // Four bytes back to where the instruction ends, and then the eight the address already
736        // meant. A relocation counts from where its own bytes start and an instruction counts
737        // from where it ends, and the addend is what makes up the difference.
738        assert_eq!(
739            text.relocs,
740            [Reloc {
741                at: 3,
742                symbol: "counter".to_owned(),
743                kind: Reference::Data,
744                addend: 4,
745                after: 0
746            }]
747        );
748    }
749
750    /// The room a patcher was promised, on both sides of the symbol.
751    ///
752    /// What holds the two halves to the same byte. The half in front of the label is written as a
753    /// byte here and the half after it is encoded from the opcode like any other instruction, so
754    /// this is what would notice if the machine ever encoded one of them as something else.
755    #[test]
756    fn an_entry_promised_to_a_patcher_is_bytes_that_do_nothing_on_both_sides_of_the_symbol() {
757        let mut names = Interner::new();
758        let mut func = Func::new(names.intern("f"));
759        let block = func.create_block();
760        let pad = Opcode::new(names.intern("x64.nop"));
761        let first = func.build(block, pad).finish();
762        func.build(block, pad).finish();
763        add(&mut func, &mut names);
764        func.patch = Some(rucc_mir::Patch { before: 3, pad, after: Some(first) });
765
766        let text = assemble(&[func], &names, &target(), true, false)
767            .expect("a function with room in it")
768            .text;
769        assert_eq!(hex(&text.bytes), "90 90 90 90 90 01 c8");
770        let [f] = &text.funcs[..] else { panic!("one function") };
771        // The symbol is after the room in front of the label and its size counts none of it, which
772        // is what makes a backtrace through the function name the function rather than the room.
773        assert_eq!(f.start, 3);
774        assert_eq!(f.len, 4);
775        // And the record points at the front of the whole thing, which here is the front of the
776        // function's bytes because there is room in front of the label.
777        assert_eq!(f.patch, Some(Patch { at: 0, before: 3 }));
778    }
779
780    /// The same when the room is all after the label, which is what one number asks for.
781    #[test]
782    fn room_that_is_all_after_the_label_is_recorded_where_it_really_starts() {
783        let mut names = Interner::new();
784        let mut func = Func::new(names.intern("f"));
785        let block = func.create_block();
786        // A landing pad in front of it, which is the one thing that goes between the label and the
787        // room and is why the record is not just the top of the function.
788        let landing = Opcode::new(names.intern("x64.endbr64"));
789        func.build(block, landing).finish();
790        let pad = Opcode::new(names.intern("x64.nop"));
791        let first = func.build(block, pad).finish();
792        func.build(block, pad).finish();
793        add(&mut func, &mut names);
794        func.patch = Some(rucc_mir::Patch { before: 0, pad, after: Some(first) });
795
796        let text = assemble(&[func], &names, &target(), true, false)
797            .expect("a function with room in it")
798            .text;
799        assert_eq!(hex(&text.bytes), "f3 0f 1e fa 90 90 01 c8");
800        let [f] = &text.funcs[..] else { panic!("one function") };
801        assert_eq!(f.start, 0);
802        assert_eq!(f.patch, Some(Patch { at: 4, before: 0 }));
803    }
804
805    #[test]
806    fn a_global_read_out_of_the_offset_table_asks_for_the_relocation_that_names_the_slot() {
807        let mut names = Interner::new();
808        let mut func = Func::new(names.intern("f"));
809        let block = func.create_block();
810        let load = Opcode::new(names.intern("x64.mov_rm_64"));
811        let away = names.intern("away");
812        func.build(block, load)
813            .operand(Operand::write(Reg::physical(RAX), GPR))
814            .mem(Mem::got(away))
815            .finish();
816
817        let text = assemble(&[func], &names, &target(), true, false)
818            .expect("a load through the offset table")
819            .text;
820        // A `mov` with a REX prefix, which the relocation requires by name: the linker is allowed
821        // to turn it back into a `lea`, and it can only do that when it knows what it is looking
822        // at down to the prefix.
823        assert_eq!(hex(&text.bytes), "48 8b 05 00 00 00 00");
824        assert_eq!(
825            text.relocs,
826            [Reloc {
827                at: 3,
828                symbol: "away".to_owned(),
829                kind: Reference::Got,
830                addend: -4,
831                after: 0
832            }]
833        );
834    }
835
836    #[test]
837    fn an_address_that_names_a_register_is_not_a_relocation() {
838        let text = write(|func, names| {
839            let block = func.create_block();
840            let lea = Opcode::new(names.intern("x64.lea_64"));
841            func.build(block, lea)
842                .operand(Operand::write(Reg::physical(RAX), GPR))
843                .mem(
844                    Mem::at(Operand::read(Reg::physical(RCX), GPR))
845                        .indexed(Operand::read(Reg::physical(RDX), GPR), 4)
846                        .plus(-16),
847                )
848                .finish();
849        });
850        assert_eq!(hex(&text.bytes), "48 8d 44 91 f0");
851        assert!(text.relocs.is_empty());
852    }
853
854    #[test]
855    fn every_function_starts_on_a_boundary_and_the_space_in_front_of_one_does_nothing() {
856        let mut names = Interner::new();
857        let mut first = Func::new(names.intern("f"));
858        add(&mut first, &mut names);
859        let mut second = Func::new(names.intern("g"));
860        add(&mut second, &mut names);
861
862        let text =
863            assemble(&[first, second], &names, &target(), true, false).expect("two functions").text;
864        assert_eq!(text.funcs[1].start, 16);
865        assert_eq!(text.bytes.len(), 18);
866        assert!(text.bytes[2..16].iter().all(|byte| *byte == NOP), "{:?}", text.bytes);
867    }
868
869    #[test]
870    fn a_function_that_was_never_allocated_is_refused_rather_than_encoded_wrongly() {
871        let mut names = Interner::new();
872        let mut func = Func::new(names.intern("f"));
873        let block = func.create_block();
874        let vreg = func.new_vreg(GPR);
875        let neg = Opcode::new(names.intern("x64.neg_r_32"));
876        func.build(block, neg).operand(Operand::write(vreg, GPR)).finish();
877        let error =
878            assemble(&[func], &names, &target(), true, false).expect_err("a virtual register");
879        assert_eq!(
880            error,
881            Error::Virtual { func: "f".to_owned(), opcode: "x64.neg_r_32".to_owned() }
882        );
883    }
884
885    #[test]
886    fn an_opcode_the_target_does_not_describe_is_refused() {
887        let mut names = Interner::new();
888        let mut func = Func::new(names.intern("f"));
889        let block = func.create_block();
890        let made_up = Opcode::new(names.intern("x64.frobnicate"));
891        func.build(block, made_up).finish();
892        let error =
893            assemble(&[func], &names, &target(), true, false).expect_err("no such instruction");
894        assert_eq!(
895            error,
896            Error::Opcode { func: "f".to_owned(), opcode: "x64.frobnicate".to_owned() }
897        );
898    }
899
900    #[test]
901    fn a_build_that_asked_for_debug_information_is_told_where_each_instruction_began() {
902        let mut names = Interner::new();
903        let mut func = Func::new(names.intern("f"));
904        let block = func.create_block();
905        let add = Opcode::new(names.intern("x64.add_rr_32"));
906        for at in 0..2u32 {
907            func.build(block, add)
908                .at(Span::new(at * 10, at * 10 + 3))
909                .operand(Operand::write(Reg::physical(RAX), GPR))
910                .operand(Operand::read(Reg::physical(RAX), GPR))
911                .operand(Operand::read(Reg::physical(RCX), GPR))
912                .finish();
913        }
914
915        let out = assemble(&[func], &names, &target(), true, true).expect("two instructions");
916        assert_eq!(
917            out.lines,
918            vec![vec![
919                Row { at: 0, span: Span::new(0, 3) },
920                Row { at: 2, span: Span::new(10, 13) },
921            ]]
922        );
923    }
924
925    #[test]
926    fn a_function_that_knows_where_it_was_declared_says_so_over_its_prologue() {
927        // The front of a function is instructions no expression in the source asked for, so
928        // nothing there carries a span and the bytes would be covered by nothing. The declaration
929        // is what gcc puts over them and it is what this puts over them too, as a row at zero in
930        // front of everything the body produced.
931        let mut names = Interner::new();
932        let mut func = Func::new(names.intern("f"));
933        func.declared = Span::new(100, 104);
934        let block = func.create_block();
935        let add = Opcode::new(names.intern("x64.add_rr_32"));
936        // The first with no span, the way every instruction a prologue is made of has none, and
937        // the second with one, the way an instruction the body asked for does.
938        for span in [Span::DUMMY, Span::new(10, 13)] {
939            func.build(block, add)
940                .at(span)
941                .operand(Operand::write(Reg::physical(RAX), GPR))
942                .operand(Operand::read(Reg::physical(RAX), GPR))
943                .operand(Operand::read(Reg::physical(RCX), GPR))
944                .finish();
945        }
946
947        let out = assemble(&[func], &names, &target(), true, true).expect("two instructions");
948        assert_eq!(
949            out.lines,
950            vec![vec![
951                Row { at: 0, span: Span::new(100, 104) },
952                Row { at: 0, span: Span::DUMMY },
953                Row { at: 2, span: Span::new(10, 13) },
954            ]]
955        );
956    }
957
958    #[test]
959    fn a_build_that_asked_for_none_carries_no_rows_at_all() {
960        let mut names = Interner::new();
961        let mut func = Func::new(names.intern("f"));
962        add(&mut func, &mut names);
963
964        let out = assemble(&[func], &names, &target(), true, false).expect("one instruction");
965        assert_eq!(out.lines, vec![Vec::new()]);
966    }
967
968    #[test]
969    fn a_machine_with_no_encoder_here_is_said_so_rather_than_encoded_as_x86_64() {
970        let names = Interner::new();
971        let aarch64 = TargetInfo::new(Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu));
972        let error = assemble(&[], &names, &aarch64, true, false).expect_err("no encoder");
973        assert!(matches!(error, Error::Machine { .. }), "{error:?}");
974    }
975}