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::{ObjectFormat, PhysReg, TargetInfo};
42use rucc_tuple::Arch;
43
44use rucc_object::{
45 Binding, Chunk, Extent, FUNC_ALIGN, Held, Marker, Patch, Reference, Reloc, Table, Text,
46 Visibility,
47};
48
49use crate::Error;
50use crate::format::{Directives, binding, visibility};
51use crate::unwind::{self, Rows};
52
53/// The prefix every x86-64 opcode carries in the machine IR.
54const PREFIX: &str = "x64.";
55
56/// The one byte instruction that does nothing, which is what the space in front of a function is.
57///
58/// Also what the room a patcher was promised is made of. The two are the same byte and not the same
59/// thing: the padding is space nothing reaches, and the room is space something jumps into once it
60/// has been written over. See `assemble`.
61const NOP: u8 = 0x90;
62
63/// Where one machine instruction ended up, and where in the source it came from.
64///
65/// The span rather than a file and a line, because this layer has no source map and no business
66/// acquiring one. Turning a span into a place is the driver's, which is also where the paths a
67/// `-ffile-prefix-map` rewrites are still paths.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct Row {
70 /// How far into its own function the instruction begins.
71 pub at: usize,
72 /// What the machine IR said this instruction was for.
73 pub span: Span,
74 /// Which instruction of the machine function it is, or `None` for the row the prologue gets,
75 /// which is the one row here that no instruction wrote.
76 ///
77 /// The line table has no use for it and the locations do: a local the allocator kept in a
78 /// register is somewhere over a stretch the back end named by an instruction at each end,
79 /// because a machine instruction has no length until something encodes it, and this is where
80 /// it gets one. Carried on the row rather than as a second list because the two are the same
81 /// walk and a second list is a thing that can come to disagree with the first.
82 pub inst: Option<Inst>,
83}
84
85/// A text section and, when the build asked for it, where each instruction in it came from.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct Assembled {
88 /// The instructions, and what the linker has to be told about them.
89 pub text: Text,
90 /// One list per function of [`Text::funcs`], in the same order, and empty throughout in a
91 /// build that asked for no debug information.
92 pub lines: Vec<Vec<Row>>,
93 /// The frame rules as `.debug_frame`, in a build that asked for debug information and for no
94 /// unwind table, where it is the only table a debugger has to find a frame base through. None
95 /// in every other build, and on a format that has no such section.
96 pub frames: Option<Chunk>,
97}
98
99/// Every function, as the bytes of a text section.
100///
101/// `unwind` is whether a function is described to an unwinder, which is
102/// `rucc_session::Options::unwinds` and is asked of the build rather than worked out here, so that
103/// this and the text writer cannot answer it differently for one function.
104///
105/// `lines` is whether to record where each instruction came from, which is
106/// `rucc_session::Options::debug_info` and is asked the same way and for the same reason. It is a
107/// question rather than something always answered because the rows are one per machine instruction
108/// and a build that is not writing debug information would carry them the length of the back end to
109/// throw them away.
110///
111/// # Errors
112///
113/// [`Error::Machine`] for an architecture nothing here encodes, and the rest for a function that
114/// should not have got this far. See [`Error`].
115///
116/// # Panics
117///
118/// Panics on a function that was promised room for a patcher and has none on either side of its
119/// own label, which is a prologue that recorded room it did not write.
120pub fn assemble(
121 funcs: &[Func],
122 names: &Interner,
123 target: &TargetInfo,
124 unwind: bool,
125 lines: bool,
126) -> Result<Assembled, Error> {
127 if target.tuple.arch() != Arch::X86_64 {
128 return Err(Error::Machine { triple: target.tuple.to_string() });
129 }
130 let mut text = Text::default();
131 let mut all = Vec::new();
132 // Where each function's frame rules landed, kept beside the extents rather than written into
133 // the section as they are found, because a record counts from the start of its function and the
134 // function's own length is not known until its last instruction has been encoded.
135 let mut rows = Vec::with_capacity(funcs.len());
136 for func in funcs {
137 // What this function asked for, which pads the space in front of it and, once every
138 // function has been through here, is what the whole section is aligned to. Both halves
139 // are needed: the offset inside the section is this padding and where the section itself
140 // lands is the alignment recorded on it. It goes on the extent as well, because under
141 // `-ffunction-sections` this function is a section of its own and the padding in front of
142 // it is gone, so this number is the only thing left saying what it wanted.
143 let align = func.align.unwrap_or(FUNC_ALIGN);
144 text.align = text.align.max(align);
145 let step = usize::try_from(align).unwrap_or(1).max(1);
146 while text.bytes.len() % step != 0 {
147 text.bytes.push(NOP);
148 }
149 // The half of the room a patcher was promised that is in front of the function's own
150 // label, laid down here because it is the one part of a finished function that is not in a
151 // block. What makes it the space in front of the function rather than the start of it is
152 // everything below: the symbol, the size and the record an unwinder reads all begin after
153 // it, which is what gcc does with the same flag and what a debugger showing a backtrace
154 // through a patched function needs.
155 //
156 // The byte is written rather than encoded because the room is counted in bytes and the
157 // instruction that fills it has no operands. `an_entry_promised_to_a_patcher_is_bytes_that
158 // _do_nothing_on_both_sides_of_the_symbol` is what holds it to the same byte the encoder
159 // writes for the half that is in a block.
160 let ahead = text.bytes.len();
161 if let Some(patch) = func.patch {
162 text.bytes.extend(std::iter::repeat_n(NOP, patch.before as usize));
163 }
164 let start = text.bytes.len();
165 let name = names.resolve(func.name).to_owned();
166 let mut assembler = Assembler {
167 names,
168 directives: Directives::of(target.object_format),
169 func,
170 name: &name,
171 text: &mut text,
172 blocks: Vec::new(),
173 jumps: Vec::new(),
174 rows: Vec::new(),
175 lines: Vec::new(),
176 wants: lines,
177 start,
178 room: None,
179 loops: Vec::new(),
180 apart: target.object_format == ObjectFormat::Elf,
181 };
182 assembler.func()?;
183 let room = assembler.room;
184 rows.push(std::mem::take(&mut assembler.rows));
185 all.push(std::mem::take(&mut assembler.lines));
186 let len = text.bytes.len() - start;
187 // Where the record points is the front of the room, which is the half in front of the
188 // label in a function that has one and the first instruction of the other half otherwise.
189 // The two are not one offset because a landing pad can sit between the halves.
190 let patch = func.patch.map(|patch| {
191 let at = if patch.before > 0 {
192 ahead
193 } else {
194 room.expect("room that is neither in front of the label nor anywhere after it")
195 };
196 Patch { at, before: patch.before as usize }
197 });
198 text.funcs.push(Extent {
199 name,
200 start,
201 len,
202 align,
203 binding: binding(func.binding),
204 visibility: visibility(func.visibility),
205 patch,
206 });
207 }
208 // In whichever of the two shapes the target reads, which is what decides whether a prologue
209 // this cannot describe is a refusal or is nothing at all. See [`unwind::table`].
210 // Or, when there is to be no unwind table and there is to be debug information, the same rows
211 // where only a debugger looks. See [`unwind::debug_frame`].
212 let mut frames = None;
213 if let Some(conv) = target.call_regs {
214 if unwind {
215 text.unwind = unwind::table(&text.funcs, &rows, conv, target.object_format)?;
216 } else if lines {
217 frames = unwind::debug_frame(&text.funcs, &rows, conv, target.object_format);
218 }
219 }
220 Ok(Assembled { text, lines: all, frames })
221}
222
223/// A template kept as text, as the bytes the assembler reads out of it on its own and the places in
224/// them that name something outside it, counted from the front of the template.
225///
226/// Read on its own when nothing in it reaches past its own text: no second section, no alignment,
227/// which counts from the front of a section this is not the front of, and no name it defines, since
228/// another template may be the one that jumps to it and the two are only put together in a
229/// listing. A numbered label it writes and goes to itself is a place rather than a name, and the
230/// reader has already turned every jump to one into a distance. What it names and does not define
231/// is left for the linker, the way gas would leave it, except for a local name, which is always in
232/// the same file and so is another template's. Anything else is an error with what about the text
233/// it was, and the unit goes to the assembler as a listing instead.
234pub(crate) fn template(
235 func: &Func,
236 block: Block,
237 inst: Inst,
238 names: &Interner,
239 directives: Directives,
240) -> Result<(Vec<u8>, Vec<Reloc>), String> {
241 // On a format whose names carry a prefix the text and the linker spell a name differently, and
242 // which one a name in the template meant is not a question this can answer.
243 if !directives.symbol().is_empty() {
244 return Err("names on this format carry a prefix".to_owned());
245 }
246 let text = crate::att::template(func, block, inst, names, directives)
247 .map_err(|trouble| trouble.to_string())?;
248 let read = crate::source::read(&format!("{}\n{text}", directives.text()), Arch::X86_64)
249 .map_err(|trouble| trouble.why)?;
250 for name in &read.names {
251 let outside = name.at == Held::Undefined
252 && name.binding == Binding::Global
253 && name.visibility == Visibility::Default
254 && !name.name.starts_with(directives.local());
255 if !outside {
256 return Err(format!("it names '{}' in a way only the whole file can say", name.name));
257 }
258 }
259 match read.parts.as_slice() {
260 [] => Ok((Vec::new(), Vec::new())),
261 [part] if part.name == ".text" && part.align <= 1 => {
262 Ok((part.bytes.clone(), part.relocs.clone()))
263 }
264 _ => Err("it writes into a section of its own or aligns what follows".to_owned()),
265 }
266}
267
268/// A jump inside a function, waiting for the block it goes to to have a place.
269struct Jump {
270 /// Where the four bytes the distance goes in begin.
271 at: usize,
272 /// Where the instruction it belongs to ends, which is what the distance is counted from.
273 end: usize,
274 /// The place it goes to.
275 to: To,
276 /// What is added to the distance, which is nothing for a jump and is the displacement for an
277 /// address that names a block and has one.
278 disp: i64,
279}
280
281/// A place in this function that an instruction can name: a block, or one of its jump tables.
282#[derive(Clone, Copy)]
283enum To {
284 Block(Block),
285 Table(u32),
286}
287
288/// One function being written out.
289struct Assembler<'a> {
290 names: &'a Interner,
291 /// How the listing spells things, which a template kept as text is filled in with before it is
292 /// read. See [`template`].
293 directives: Directives,
294 func: &'a Func,
295 name: &'a str,
296 text: &'a mut Text,
297 /// Where each block starts, indexed by the block's own number, or [`usize::MAX`] for one that
298 /// is not in the layout.
299 blocks: Vec<usize>,
300 jumps: Vec<Jump>,
301 /// The frame rules, each with how far into this function the instruction that changed them
302 /// ended.
303 rows: Rows,
304 /// Where each machine instruction began and what it was for, in the order they were written.
305 ///
306 /// Empty in a build that asked for no debug information, which is what `wants` says.
307 lines: Vec<Row>,
308 /// Whether to fill `lines` in at all.
309 wants: bool,
310 /// Where this function starts in the section, which is what those distances are counted from.
311 start: usize,
312 /// Where the room a patcher was promised after the label began, which is where the instruction
313 /// [`rucc_mir::Patch::after`] names was encoded.
314 ///
315 /// [`None`] in a function that was promised none and in one whose room is all in front of the
316 /// label, which is the same answer to two different questions and is why the caller decides
317 /// which of them it asked. See `assemble`.
318 room: Option<usize>,
319 /// How long the loop each block is the head of is, indexed by the block's own number, and zero
320 /// for a block that heads none. See [`loop_sizes`].
321 loops: Vec<usize>,
322 /// Whether the jump tables go in `.rodata` rather than after the code, which they do on ELF.
323 /// See [`Self::tables`].
324 apart: bool,
325}
326
327/// How long each loop in the function is, from its head to the end of the last jump back to it,
328/// indexed by the head's own number and zero for a block that is not a head.
329///
330/// Worked out by laying the function out once with no padding and throwing the bytes away. That is
331/// exact because every jump here is four bytes of distance whatever the distance is, so no
332/// instruction's length depends on where it lands and padding in front of the head moves the whole
333/// loop without changing its size. The cost is encoding a function twice, and only a function
334/// something asked to pad a loop in pays it.
335pub(crate) fn loop_sizes(
336 names: &Interner,
337 directives: Directives,
338 func: &Func,
339) -> Result<Vec<usize>, Error> {
340 let mut sizes = vec![0; func.block_count()];
341 if func.heads.is_empty() {
342 return Ok(sizes);
343 }
344 let mut text = Text::default();
345 let mut scratch = Assembler {
346 names,
347 directives,
348 func,
349 name: "",
350 text: &mut text,
351 blocks: Vec::new(),
352 jumps: Vec::new(),
353 rows: Vec::new(),
354 lines: Vec::new(),
355 wants: false,
356 start: 0,
357 room: None,
358 loops: Vec::new(),
359 apart: false,
360 };
361 scratch.lay()?;
362 for jump in &scratch.jumps {
363 let To::Block(head) = jump.to else { continue };
364 let start = scratch.blocks[head.index()];
365 // A jump that ends in front of the head is the way into the loop and not the way round it.
366 if start == usize::MAX || jump.end <= start || !func.heads.contains(&head) {
367 continue;
368 }
369 sizes[head.index()] = sizes[head.index()].max(jump.end - start);
370 }
371 Ok(sizes)
372}
373
374impl Assembler<'_> {
375 /// The blocks, and then the jumps between them once every block has a place.
376 fn func(&mut self) -> Result<(), Error> {
377 self.loops = loop_sizes(self.names, self.directives, self.func)?;
378 self.lay()?;
379 let tables = self.tables()?;
380 self.patch(&tables)
381 }
382
383 /// The blocks, one after another, with the jumps between them left for [`Self::patch`].
384 fn lay(&mut self) -> Result<(), Error> {
385 self.blocks = vec![usize::MAX; self.func.block_count()];
386 // The prologue, first, because nothing in it has a span of its own. The pushes, the frame
387 // and the moves that put the arguments where the body expects them came from no expression
388 // in the source, so without this the front of every function is the one part of it no row
389 // covers, and a program counter in there gets no answer at all rather than a slightly
390 // early one. Where the function was declared is what gcc says over those bytes.
391 if self.wants && !self.func.declared.is_dummy() {
392 self.lines.push(Row { at: 0, span: self.func.declared, inst: None });
393 }
394 let end = self.func.cfi_end();
395 for block in self.func.blocks() {
396 // The head of a loop is padded the way the listing asks the assembler to pad it, with
397 // instructions rather than single bytes, since the block in front of it may fall in.
398 // The section is told for the reason an alignment instruction tells it below, since a
399 // place inside a line of the section is one inside a line of memory only if the
400 // section starts on one.
401 let size = self.loops.get(block.index()).copied().unwrap_or(0);
402 if crate::loop_room(size).is_some() {
403 let count = crate::loop_padding(self.text.bytes.len(), size);
404 x86_64::nops(count, &mut self.text.bytes);
405 self.text.align = self.text.align.max(crate::LINE as u32);
406 }
407 self.blocks[block.index()] = self.text.bytes.len();
408 // And the name an image knows the block by, as a symbol at the same byte. The number
409 // the jumps above use is worked out here and stays here, because both ends of a jump
410 // are in this section. An image is in another one, so what it holds is a relocation
411 // and a relocation names a symbol, which is what this is.
412 if let Some(label) = self.func.block_name(block) {
413 let name = self.names.resolve(label).to_owned();
414 self.text.labels.push(Marker { name, at: self.text.bytes.len() });
415 }
416 for inst in self.func.insts(block) {
417 // Before it is encoded, because what is wanted is where it begins and after this
418 // it has already been written. A landing pad is in front of it in a function that
419 // has one, which is why the room is found this way rather than measured from the
420 // top of the function.
421 if self.func.patch.is_some_and(|patch| patch.after == Some(inst)) {
422 self.room = Some(self.text.bytes.len());
423 }
424 // Where it begins rather than where it ends, which is the other way round from the
425 // frame rules below and for the same reason they are that way round: a debugger is
426 // asking what a program counter is in the middle of, and an unwinder is asking what
427 // the frame looked like at a return address.
428 if self.wants {
429 let at = self.text.bytes.len() - self.start;
430 self.lines.push(Row { at, span: self.func.span(inst), inst: Some(inst) });
431 }
432 self.inst(block, inst)?;
433 if Some(inst) == end {
434 continue;
435 }
436 // Where the instruction ended, because a row takes effect after the instruction
437 // that changed the answer and an unwinder is looking up a return address, which is
438 // the byte after a call rather than the call itself.
439 let at = self.text.bytes.len() - self.start;
440 self.rows.extend(self.func.cfi_after(inst).map(|op| (at, op)));
441 }
442 }
443 Ok(())
444 }
445
446 /// Where the jumps go, now that every block and every table has a place.
447 fn patch(&mut self, tables: &[usize]) -> Result<(), Error> {
448 for jump in std::mem::take(&mut self.jumps) {
449 let to = match jump.to {
450 To::Block(block) => self.blocks[block.index()],
451 To::Table(table) => tables[table as usize],
452 };
453 debug_assert_ne!(to, usize::MAX, "a jump to a block that was never laid out");
454 let distance = i64::try_from(to).expect("a section this size") + jump.disp
455 - i64::try_from(jump.end).expect("a section this size");
456 let distance = i32::try_from(distance)
457 .map_err(|_| Error::Distance { func: self.name.to_owned(), bytes: distance })?;
458 self.text.bytes[jump.at..jump.at + 4].copy_from_slice(&distance.to_le_bytes());
459 }
460 Ok(())
461 }
462
463 /// The jump tables, giving back where each one starts when it is in these bytes.
464 ///
465 /// On ELF each goes in `.rodata`, which is where gcc and clang put one: a table is read and
466 /// never run, and in the code it takes room in the lines the instruction fetcher reads and is
467 /// counted as code by anything that measures a section. What goes to the writer is which block
468 /// each cell names, counted from the front of the function, and the writer makes each cell a
469 /// relocation, since its two ends are no longer in one section. See [`Table`].
470 ///
471 /// On the other formats the table stays after the last instruction, where every cell is a
472 /// distance from the table to a block with both ends in this section, so the whole table is
473 /// filled in here and the linker is told nothing. The cells are four bytes each and start on a
474 /// four byte boundary, reached by the byte that does nothing, although nothing ever runs into
475 /// it: the last instruction of a function is a return or a jump.
476 fn tables(&mut self) -> Result<Vec<usize>, Error> {
477 let mut starts = Vec::with_capacity(self.func.tables.len());
478 if self.func.tables.is_empty() {
479 return Ok(starts);
480 }
481 if self.apart {
482 for (index, table) in self.func.tables.iter().enumerate() {
483 let block =
484 self.func.block_of(table.jump).expect("a table read by a jump in no block");
485 let succs = &self.func[block].succs;
486 let cells = table
487 .cells
488 .iter()
489 .map(|&cell| {
490 let to = self.blocks[succs[cell as usize].block.index()];
491 debug_assert_ne!(to, usize::MAX, "a table naming a block never laid out");
492 to - self.start
493 })
494 .collect();
495 let name = self.table(index);
496 self.text.tables.push(Table { name, func: self.text.funcs.len(), cells });
497 }
498 return Ok(starts);
499 }
500 while self.text.bytes.len() % 4 != 0 {
501 self.text.bytes.push(NOP);
502 }
503 for table in &self.func.tables {
504 let start = self.text.bytes.len();
505 starts.push(start);
506 let block = self.func.block_of(table.jump).expect("a table read by a jump in no block");
507 let succs = &self.func[block].succs;
508 for &cell in &table.cells {
509 let to = self.blocks[succs[cell as usize].block.index()];
510 debug_assert_ne!(to, usize::MAX, "a table naming a block that was never laid out");
511 let distance = i64::try_from(to).expect("a section this size")
512 - i64::try_from(start).expect("a section this size");
513 let distance = i32::try_from(distance)
514 .map_err(|_| Error::Distance { func: self.name.to_owned(), bytes: distance })?;
515 self.text.bytes.extend_from_slice(&distance.to_le_bytes());
516 }
517 }
518 Ok(starts)
519 }
520
521 /// The name one jump table of this function goes by, which is the one the listing gives it.
522 fn table(&self, index: usize) -> String {
523 format!("{}{}_j{index}", self.directives.local(), self.name)
524 }
525
526 /// One instruction of the machine IR, as however many instructions of the machine it is.
527 fn inst(&mut self, block: Block, inst: Inst) -> Result<(), Error> {
528 let data = self.func[inst];
529 let spelled = self.names.resolve(data.opcode.name());
530 let opcode = spelled.strip_prefix(PREFIX).unwrap_or(spelled);
531 // The one opcode that is not an instruction. Where the listing writes the assembler's own
532 // directive this has to do what the assembler would have done, which is pad up to the
533 // boundary with the byte that does nothing, since the gap is reached by falling into it.
534 //
535 // The section has to be told as well. The padding puts the next instruction at a multiple of
536 // the boundary counted from the front of the section, and what makes that an address the
537 // program sees is the section itself landing on one, so the boundary goes on the section's
538 // alignment the way a function's own does.
539 if opcode == x86_64::ALIGN {
540 let bytes = data.imm.map_or(0, |imm| self.func[imm].0);
541 let boundary = u32::try_from(bytes).ok().filter(|at| at.is_power_of_two());
542 let Some(boundary) = boundary else {
543 return Err(Error::Opcode {
544 func: self.name.to_owned(),
545 opcode: spelled.to_owned(),
546 });
547 };
548 self.text.align = self.text.align.max(boundary);
549 let step = boundary as usize;
550 while self.text.bytes.len() % step != 0 {
551 self.text.bytes.push(NOP);
552 }
553 return Ok(());
554 }
555 // The other one, which is the bytes a template wrote out as themselves. There is nothing to
556 // encode: the program already said what the processor is to be handed, so they go down as
557 // they are.
558 if opcode == x86_64::LITERAL {
559 let Some(imm) = data.imm else {
560 return Err(Error::Opcode {
561 func: self.name.to_owned(),
562 opcode: spelled.to_owned(),
563 });
564 };
565 let before = self.text.bytes.len();
566 self.text.bytes.extend(x86_64::unpacked(self.func[imm].0));
567 if self.text.bytes.len() == before {
568 return Err(Error::Opcode {
569 func: self.name.to_owned(),
570 opcode: spelled.to_owned(),
571 });
572 }
573 return Ok(());
574 }
575 // A template kept as text, read on its own and laid down as what it came to. One the
576 // reader cannot take on its own sends the whole unit to the assembler as a listing instead
577 // and never comes here, see [`crate::kept`], so a refusal here is that check and this one
578 // disagreeing.
579 if opcode == x86_64::TEMPLATE {
580 let (bytes, relocs) =
581 template(self.func, block, inst, self.names, self.directives).map_err(|why| {
582 Error::Encode { func: self.name.to_owned(), opcode: spelled.to_owned(), why }
583 })?;
584 let at = self.text.bytes.len();
585 self.text.bytes.extend(bytes);
586 self.text
587 .relocs
588 .extend(relocs.into_iter().map(|reloc| Reloc { at: reloc.at + at, ..reloc }));
589 return Ok(());
590 }
591 let Some(written) = x86_64::written(opcode) else {
592 return Err(Error::Opcode { func: self.name.to_owned(), opcode: spelled.to_owned() });
593 };
594 let operands = &self.func[data.operands];
595 for machine in written {
596 // What each argument turned out to be, and what the encoder has to be told about
597 // afterwards for the ones that name something it cannot see.
598 let mut values = Vec::with_capacity(machine.args.len());
599 let mut wanted = None;
600 // The other thing an address can name, which is a place in this same function and so is
601 // a distance nothing outside the file has to be told about.
602 let mut labelled = None;
603 for arg in machine.args {
604 values.push(match *arg {
605 Arg::Reg(at, width) => {
606 Value::Reg(self.phys(operands[usize::from(at)], spelled)?, width)
607 }
608 // The same thing in the other file, which the encoder has to be told apart
609 // from the one above: which file a register is in is part of which instruction
610 // it is, and the table it looks a row up in is what says so.
611 Arg::Xmm(at) => Value::Xmm(self.phys(operands[usize::from(at)], spelled)?),
612 // The two halves of one word. The encoder numbers a high byte as the low one
613 // plus four, which is the whole of the difference between them in the bytes
614 // and is also why only the first four registers have one.
615 Arg::Low(at) => {
616 Value::Reg(self.phys(operands[usize::from(at)], spelled)?, Width::Byte)
617 }
618 Arg::High(at) => Value::High(self.phys(operands[usize::from(at)], spelled)?),
619 // The only register named outright on this machine is the high half of the
620 // first one, which an eight bit remainder comes back in.
621 Arg::Named(_) => Value::High(RAX),
622 // A depth on the x87 stack, which carries nothing across because there is
623 // nothing to carry: the depth is in the opcode byte the mnemonic picks, so
624 // what the encoder needs from here is that an argument was there at all.
625 Arg::Stack(_) => Value::Stack,
626 Arg::Lit(lane) => Value::Imm(i64::from(lane)),
627 // The first operand read, which is where a call puts the address it goes
628 // through. Everything in front of it is a register the call writes.
629 Arg::Through => {
630 Value::Reg(self.phys(operands[defs(operands)], spelled)?, Width::Quad)
631 }
632 Arg::Imm => Value::Imm(data.imm.map_or(0, |imm| self.func[imm].0)),
633 Arg::Mem => {
634 let amode = data.mem.map(|mem| self.func[mem]);
635 let (addr, symbol) = self.addr(operands, amode.as_ref(), spelled)?;
636 if let Some(symbol) = symbol {
637 // A mode that reads the global offset table names the slot rather than
638 // the thing, and the four bytes are the same four bytes either way, so
639 // which relocation it is is the whole of the difference here.
640 let kind = match amode.map_or(Reach::Itself, |mem| mem.reach) {
641 Reach::Itself => Reference::Data,
642 Reach::Table => Reference::Got,
643 Reach::Thread => Reference::Thread,
644 };
645 wanted = Some((symbol, kind, i64::from(addr.disp)));
646 }
647 if let Some(block) = amode.and_then(|mem| mem.block) {
648 labelled = Some((To::Block(block), i64::from(addr.disp)));
649 }
650 if let Some(table) = amode.and_then(|mem| mem.table) {
651 // In another section, so the linker's to fill in like any symbol.
652 if self.apart && addr.rip {
653 let name = self.table(table as usize);
654 wanted = Some((name, Reference::Data, i64::from(addr.disp)));
655 } else {
656 labelled = Some((To::Table(table), i64::from(addr.disp)));
657 }
658 }
659 Value::Mem(addr)
660 }
661 Arg::Symbol => {
662 let symbol =
663 data.symbol.map(|symbol| self.names.resolve(symbol).to_owned());
664 if let Some(symbol) = symbol {
665 wanted = Some((symbol, Reference::Call, 0));
666 }
667 Value::Dest
668 }
669 // Where a conditional jump goes is the first arm, because the block layout
670 // guarantees the second is the block laid out next and is fallen into.
671 Arg::Label => Value::Dest,
672 });
673 }
674
675 let start = self.text.bytes.len();
676 let holes =
677 x86_64::encode(machine.mnemonic, &values, &mut self.text.bytes).map_err(|why| {
678 Error::Encode {
679 func: self.name.to_owned(),
680 opcode: spelled.to_owned(),
681 why: why.to_string(),
682 }
683 })?;
684 let end = self.text.bytes.len();
685
686 // A hole is either something outside the file, which is a relocation, or a block of
687 // this function, which is patched once every block has a place.
688 if let Some((symbol, kind, disp)) = wanted {
689 let kind = match kind {
690 Reference::Got => slot(&self.text.bytes[start..end]),
691 kind => kind,
692 };
693 let at = match kind {
694 Reference::Call => holes.dest,
695 Reference::Data
696 | Reference::Got
697 | Reference::GotBare
698 | Reference::GotKept
699 | Reference::Thread => holes.rip,
700 // An address written into an image rather than reached by an instruction, and
701 // how far something is from the front of one, which is what a table of data
702 // holds. Nothing above produces either, because every reference an instruction
703 // makes is a distance from where the instruction ends. The field of an AArch64
704 // instruction is not something this machine has.
705 Reference::Address { .. }
706 | Reference::Image
707 | Reference::Away
708 | Reference::Field(_) => {
709 unreachable!("an instruction wanting an address")
710 }
711 };
712 let at = at.expect("an instruction naming a symbol leaves room for the distance");
713 let addend = disp - i64::try_from(end - at).expect("an instruction this long");
714 // How many bytes of the instruction come after the four the linker writes over,
715 // which is what is left of the distance from the hole to the end of it. Already in
716 // the addend and written down again because COFF wants the two apart, and there is
717 // nowhere else it can be worked out: by the time a writer sees the relocation the
718 // instruction it is in is bytes like any others.
719 let after = u8::try_from(end - at - 4).expect("an instruction this long");
720 self.text.relocs.push(Reloc { at, symbol, kind, addend, after });
721 // The addend is the whole of it, so the four bytes are left as nothing, which is
722 // what gas leaves. tcc's linker adds to what is there rather than writing over it,
723 // and a `mov cstr_buf+8(%rip)` with the eight in both places read eight bytes
724 // past the member it wanted.
725 self.text.bytes[at..at + 4].fill(0);
726 } else if let Some((to, disp)) = labelled {
727 // The address of a label, which is the four bytes an address counted from the
728 // instruction pointer leaves and is patched where a jump is patched rather than
729 // written out as a relocation, since both ends of it are in this function.
730 let at = holes.rip.expect("an address naming a label leaves room for the distance");
731 self.jumps.push(Jump { at, end, to, disp });
732 } else if let Some(at) = holes.dest {
733 match self.func[block].succs.first() {
734 Some(call) => {
735 self.jumps.push(Jump { at, end, to: To::Block(call.block), disp: 0 });
736 }
737 None => debug_assert!(false, "a jump out of a block with no arms"),
738 }
739 }
740 }
741 Ok(())
742 }
743
744 /// One address, with the operands it names resolved and the symbol it names handed back.
745 ///
746 /// A symbol with no base and no index is reached from the instruction pointer, which is how a
747 /// global is reached in position independent code and the only way this compiler reaches one.
748 /// The displacement is carried to the relocation's addend, and the four bytes it would have
749 /// gone in are left as nothing once the relocation is written.
750 fn addr(
751 &self,
752 operands: &[Operand],
753 amode: Option<&Amode>,
754 opcode: &str,
755 ) -> Result<(Addr, Option<String>), Error> {
756 let Some(amode) = amode else {
757 return Ok((Addr::default(), None));
758 };
759 let base = match amode.base {
760 Some(at) => Some(self.phys(operands[usize::from(at)], opcode)?),
761 None => None,
762 };
763 let index = match amode.index {
764 Some(at) => Some(self.phys(operands[usize::from(at)], opcode)?),
765 None => None,
766 };
767 let symbol = amode.symbol.map(|symbol| self.names.resolve(symbol).to_owned());
768 // A block is reached the same way and leaves the same four bytes. What is different is who
769 // fills them in, which is this file rather than the linker, and that is the caller's to
770 // sort out: what it needs from here is that the address was written that way at all.
771 let names = symbol.is_some() || amode.block.is_some() || amode.table.is_some();
772 let rip = names && base.is_none() && index.is_none();
773 let addr =
774 Addr { base, index, scale: amode.scale, disp: amode.disp, rip, segment: amode.segment };
775 Ok((addr, if rip { symbol } else { None }))
776 }
777
778 /// The real register one operand ended up in.
779 fn phys(&self, operand: Operand, opcode: &str) -> Result<PhysReg, Error> {
780 operand
781 .reg
782 .phys()
783 .ok_or_else(|| Error::Virtual { func: self.name.to_owned(), opcode: opcode.to_owned() })
784 }
785}
786
787/// Which relocation a read of a slot of the global offset table asks for, from the bytes of the
788/// instruction it is in.
789///
790/// The linker can turn a slot back into the address itself in only a few instructions: a `mov`
791/// from memory, `test`, the eight that do arithmetic from memory into a register, and a `call` or
792/// `jmp` through memory, none of them behind a `0x66`. gas asks for the relocation that allows it
793/// in those and the plain one everywhere else, and says whether there is a REX prefix, which is
794/// what the linker needs to know to rewrite the instruction in place.
795pub(crate) fn slot(bytes: &[u8]) -> Reference {
796 let mut rest = bytes;
797 let mut rex = false;
798 while let [first, tail @ ..] = rest {
799 match first {
800 0x66 => return Reference::GotKept,
801 0x26 | 0x2E | 0x36 | 0x3E | 0x64 | 0x65 | 0x67 | 0xF0 | 0xF2 | 0xF3 => rest = tail,
802 0x40..=0x4F => {
803 rex = true;
804 rest = tail;
805 }
806 _ => break,
807 }
808 }
809 let rewritten = match rest {
810 [0x8B | 0x85, ..] => true,
811 [0xFF, modrm, ..] => matches!((modrm >> 3) & 7, 2 | 4),
812 [op, ..] => *op & !0x38 == 0x03,
813 [] => false,
814 };
815 match (rewritten, rex) {
816 (false, _) => Reference::GotKept,
817 (true, true) => Reference::Got,
818 (true, false) => Reference::GotBare,
819 }
820}
821
822#[cfg(test)]
823mod tests {
824 use super::*;
825
826 use rucc_base::Interner;
827 use rucc_mir::{BlockCall, Mem, Opcode, Reg, Table};
828 use rucc_object::{Binding, Visibility};
829 use rucc_target::x86_64::{GPR, RAX, RCX, RDX};
830 use rucc_target::{Arch, Env, Os, Triple};
831
832 /// A linux x86-64 target, which is the one every case here is written for.
833 fn target() -> TargetInfo {
834 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
835 }
836
837 /// One function of one block, with those instructions in it, assembled.
838 fn write(build: impl FnOnce(&mut Func, &mut Interner)) -> Text {
839 let mut names = Interner::new();
840 let mut func = Func::new(names.intern("f"));
841 build(&mut func, &mut names);
842 assemble(&[func], &names, &target(), true, false)
843 .expect("a function that was allocated")
844 .text
845 }
846
847 /// Those bytes, as the hexadecimal a manual writes them in.
848 fn hex(bytes: &[u8]) -> String {
849 bytes.iter().map(|byte| format!("{byte:02x}")).collect::<Vec<_>>().join(" ")
850 }
851
852 /// An addition of two registers, which is the smallest instruction with operands there is.
853 fn add(func: &mut Func, names: &mut Interner) {
854 let block = func.create_block();
855 let add = Opcode::new(names.intern("x64.add_rr_32"));
856 func.build(block, add)
857 .operand(Operand::write(Reg::physical(RAX), GPR))
858 .operand(Operand::read(Reg::physical(RAX), GPR))
859 .operand(Operand::read(Reg::physical(RCX), GPR))
860 .finish();
861 }
862
863 #[test]
864 fn an_instruction_is_the_bytes_the_target_says_it_is() {
865 let text = write(add);
866 assert_eq!(hex(&text.bytes), "01 c8");
867 let f = Extent {
868 name: "f".to_owned(),
869 start: 0,
870 len: 2,
871 align: FUNC_ALIGN,
872 binding: Binding::Global,
873 visibility: Visibility::Default,
874 patch: None,
875 };
876 assert_eq!(text.funcs, [f]);
877 assert!(text.relocs.is_empty());
878 }
879
880 #[test]
881 fn an_opcode_the_machine_has_no_single_instruction_for_is_all_the_ones_it_has() {
882 let text = write(|func, names| {
883 let block = func.create_block();
884 let cmp = Opcode::new(names.intern("x64.cmp_set_l_64"));
885 func.build(block, cmp)
886 .operand(Operand::write(Reg::physical(RAX), GPR))
887 .operand(Operand::read(Reg::physical(RCX), GPR))
888 .operand(Operand::read(Reg::physical(RDX), GPR))
889 .finish();
890 });
891 // The comparison at the width it was asked for and then the set, which is the same two
892 // instructions the assembly path writes and is why one description rather than two.
893 assert_eq!(hex(&text.bytes), "48 39 d1 0f 9c c0");
894 }
895
896 #[test]
897 fn an_opcode_that_is_not_an_instruction_is_no_bytes_at_all() {
898 let text = write(|func, names| {
899 let block = func.create_block();
900 let ret = Opcode::new(names.intern("x64.ret_val_32"));
901 func.build(block, ret).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
902 });
903 assert!(text.bytes.is_empty(), "{:?}", text.bytes);
904 }
905
906 #[test]
907 fn an_alignment_is_the_bytes_between_where_it_is_and_the_boundary_it_asks_for() {
908 let text = write(|func, names| {
909 let block = func.create_block();
910 let add = Opcode::new(names.intern("x64.add_rr_32"));
911 let align = Opcode::new(names.intern("x64.align"));
912 let two = |func: &mut Func| {
913 func.build(block, add)
914 .operand(Operand::write(Reg::physical(RAX), GPR))
915 .operand(Operand::read(Reg::physical(RAX), GPR))
916 .operand(Operand::read(Reg::physical(RCX), GPR))
917 .finish();
918 };
919 two(func);
920 func.build(block, align).imm(8).finish();
921 two(func);
922 });
923 // Two bytes of addition, six of nothing, two more of addition. The padding is the one byte
924 // instruction that does nothing rather than a run of zeroes, because the processor may walk
925 // through it to get to what comes after, which is the whole reason a program asks.
926 assert_eq!(hex(&text.bytes), "01 c8 90 90 90 90 90 90 01 c8");
927 // The section has to be told as well. A function aligned to eight inside a section aligned
928 // to one is aligned to eight in its own reckoning and to nothing at all in the program's.
929 assert!(text.align >= 8, "{}", text.align);
930 }
931
932 /// The bytes a template wrote out itself, which go down as they are.
933 ///
934 /// `xgetbv` written as its three bytes, which is how every program that has one writes it,
935 /// between two instructions so that what is checked is that the bytes land where the program
936 /// put them and not just that they land.
937 #[test]
938 fn a_byte_out_of_a_template_is_that_byte_and_nothing_around_it() {
939 let text = write(|func, names| {
940 let block = func.create_block();
941 let add = Opcode::new(names.intern("x64.add_rr_32"));
942 let byte = Opcode::new(names.intern("x64.byte"));
943 let two = |func: &mut Func| {
944 func.build(block, add)
945 .operand(Operand::write(Reg::physical(RAX), GPR))
946 .operand(Operand::read(Reg::physical(RAX), GPR))
947 .operand(Operand::read(Reg::physical(RCX), GPR))
948 .finish();
949 };
950 two(func);
951 let bytes = x86_64::packed(&[0x0f, 0x01, 0xd0]).expect("three bytes fit");
952 func.build(block, byte).imm(bytes).finish();
953 two(func);
954 });
955 assert_eq!(hex(&text.bytes), "01 c8 0f 01 d0 01 c8");
956 }
957
958 #[test]
959 fn a_jump_inside_a_function_is_filled_in_rather_than_left_to_the_linker() {
960 let mut names = Interner::new();
961 let mut func = Func::new(names.intern("f"));
962 let first = func.create_block();
963 let second = func.create_block();
964 let add = Opcode::new(names.intern("x64.add_rr_32"));
965 func.build(first, add)
966 .operand(Operand::write(Reg::physical(RAX), GPR))
967 .operand(Operand::read(Reg::physical(RAX), GPR))
968 .operand(Operand::read(Reg::physical(RCX), GPR))
969 .finish();
970 let jmp = Opcode::new(names.intern("x64.jmp"));
971 func.build(second, jmp).finish();
972 func.succs_mut(second).push(BlockCall::to(first));
973
974 let text = assemble(&[func], &names, &target(), true, false).expect("two blocks").text;
975 // Two bytes of addition, then a jump back over itself and over them, which is seven bytes
976 // backwards because a jump counts from where it ends.
977 assert_eq!(hex(&text.bytes), "01 c8 e9 f9 ff ff ff");
978 assert!(text.relocs.is_empty(), "a jump inside a function is not the linker's business");
979 }
980
981 #[test]
982 fn the_address_of_a_label_is_filled_in_here_as_well() {
983 let mut names = Interner::new();
984 let mut func = Func::new(names.intern("f"));
985 let first = func.create_block();
986 let second = func.create_block();
987 let lea = Opcode::new(names.intern("x64.lea_64"));
988 func.build(first, lea)
989 .operand(Operand::write(Reg::physical(RAX), GPR))
990 .mem(Mem::block(second))
991 .finish();
992 let jmp = Opcode::new(names.intern("x64.jmp_reg"));
993 func.build(first, jmp).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
994 func.succs_mut(first).push(BlockCall::to(second));
995 func.build(second, Opcode::new(names.intern("x64.ret"))).finish();
996
997 let text = assemble(&[func], &names, &target(), true, false).expect("two blocks").text;
998 // Seven bytes of address, two of jump, and then the block. The distance is two, because
999 // the four bytes count from the end of the instruction that holds them and the jump is
1000 // what is in between.
1001 assert_eq!(hex(&text.bytes), "48 8d 05 02 00 00 00 ff e0 c3");
1002 assert!(text.relocs.is_empty(), "a label of this function is not the linker's business");
1003 }
1004
1005 /// A function that jumps through a table of three cells to one of two returns.
1006 fn switching(names: &mut Interner) -> Func {
1007 let mut func = Func::new(names.intern("f"));
1008 let head = func.create_block();
1009 let first = func.create_block();
1010 let second = func.create_block();
1011 let lea = Opcode::new(names.intern("x64.lea_64"));
1012 func.build(head, lea)
1013 .operand(Operand::write(Reg::physical(RAX), GPR))
1014 .mem(Mem::table(0))
1015 .finish();
1016 let jmp = Opcode::new(names.intern("x64.jmp_reg"));
1017 let jump = func.build(head, jmp).operand(Operand::read(Reg::physical(RAX), GPR)).finish();
1018 func.succs_mut(head).push(BlockCall::to(first));
1019 func.succs_mut(head).push(BlockCall::to(second));
1020 func.build(first, Opcode::new(names.intern("x64.ret"))).finish();
1021 func.build(second, Opcode::new(names.intern("x64.ret"))).finish();
1022 func.tables.push(Table { jump, cells: vec![0, 1, 0] });
1023 func
1024 }
1025
1026 #[test]
1027 fn a_jump_table_on_elf_goes_to_the_writer_with_where_each_block_is() {
1028 let mut names = Interner::new();
1029 let func = switching(&mut names);
1030 let text = assemble(&[func], &names, &target(), true, false).expect("a table").text;
1031 // Seven bytes of address, two of jump and two returns, and nothing after them: the table
1032 // is not in the code. The address is the linker's to fill in, counted from the end of
1033 // the instruction, which is four bytes past the hole.
1034 assert_eq!(hex(&text.bytes), "48 8d 05 00 00 00 00 ff e0 c3 c3");
1035 assert_eq!(
1036 text.relocs,
1037 [Reloc {
1038 at: 3,
1039 symbol: ".Lf_j0".to_owned(),
1040 kind: Reference::Data,
1041 addend: -4,
1042 after: 0
1043 }]
1044 );
1045 // The two returns are nine and ten bytes into the function.
1046 let table =
1047 rucc_object::Table { name: ".Lf_j0".to_owned(), func: 0, cells: vec![9, 10, 9] };
1048 assert_eq!(text.tables, [table]);
1049 }
1050
1051 #[test]
1052 fn a_jump_table_on_windows_is_written_after_the_code_as_distances_from_itself() {
1053 let mut names = Interner::new();
1054 let func = switching(&mut names);
1055 let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Windows, Env::Gnu));
1056 let text = assemble(&[func], &names, &target, true, false).expect("a table").text;
1057 // Seven bytes of address, two of jump and two returns end at eleven, one byte that does
1058 // nothing brings the table to twelve, and each cell is how far back its block is from
1059 // there. The address counts from the end of its own instruction, so it is five.
1060 assert_eq!(
1061 hex(&text.bytes),
1062 "48 8d 05 05 00 00 00 ff e0 c3 c3 90 fd ff ff ff fe ff ff ff fd ff ff ff"
1063 );
1064 assert!(text.relocs.is_empty(), "a table of this function is not the linker's business");
1065 assert!(text.tables.is_empty(), "{:?}", text.tables);
1066 }
1067
1068 #[test]
1069 fn a_call_leaves_the_linker_the_name_of_what_it_calls() {
1070 let mut names = Interner::new();
1071 let mut func = Func::new(names.intern("f"));
1072 let block = func.create_block();
1073 let call = Opcode::new(names.intern("x64.call"));
1074 let callee = names.intern("puts");
1075 func.build(block, call).symbol(callee).finish();
1076
1077 let text = assemble(&[func], &names, &target(), true, false).expect("a call").text;
1078 assert_eq!(hex(&text.bytes), "e8 00 00 00 00");
1079 assert_eq!(
1080 text.relocs,
1081 [Reloc {
1082 at: 1,
1083 symbol: "puts".to_owned(),
1084 kind: Reference::Call,
1085 addend: -4,
1086 after: 0
1087 }]
1088 );
1089 }
1090
1091 #[test]
1092 fn a_global_is_a_relocation_counted_from_the_end_of_the_instruction() {
1093 let mut names = Interner::new();
1094 let mut func = Func::new(names.intern("f"));
1095 let block = func.create_block();
1096 let load = Opcode::new(names.intern("x64.mov_rm_64"));
1097 let global = names.intern("counter");
1098 func.build(block, load)
1099 .operand(Operand::write(Reg::physical(RAX), GPR))
1100 .mem(Mem::of(global).plus(8))
1101 .finish();
1102
1103 let text =
1104 assemble(&[func], &names, &target(), true, false).expect("a load of a global").text;
1105 // The four bytes are nothing, as gas leaves them, because tcc's linker adds to what is
1106 // there and would count the eight twice.
1107 assert_eq!(hex(&text.bytes), "48 8b 05 00 00 00 00");
1108 // Four bytes back to where the instruction ends, and then the eight the address already
1109 // meant. A relocation counts from where its own bytes start and an instruction counts
1110 // from where it ends, and the addend is what makes up the difference.
1111 assert_eq!(
1112 text.relocs,
1113 [Reloc {
1114 at: 3,
1115 symbol: "counter".to_owned(),
1116 kind: Reference::Data,
1117 addend: 4,
1118 after: 0
1119 }]
1120 );
1121 }
1122
1123 /// The room a patcher was promised, on both sides of the symbol.
1124 ///
1125 /// What holds the two halves to the same byte. The half in front of the label is written as a
1126 /// byte here and the half after it is encoded from the opcode like any other instruction, so
1127 /// this is what would notice if the machine ever encoded one of them as something else.
1128 #[test]
1129 fn an_entry_promised_to_a_patcher_is_bytes_that_do_nothing_on_both_sides_of_the_symbol() {
1130 let mut names = Interner::new();
1131 let mut func = Func::new(names.intern("f"));
1132 let block = func.create_block();
1133 let pad = Opcode::new(names.intern("x64.nop"));
1134 let first = func.build(block, pad).finish();
1135 func.build(block, pad).finish();
1136 add(&mut func, &mut names);
1137 func.patch = Some(rucc_mir::Patch { before: 3, pad, after: Some(first) });
1138
1139 let text = assemble(&[func], &names, &target(), true, false)
1140 .expect("a function with room in it")
1141 .text;
1142 assert_eq!(hex(&text.bytes), "90 90 90 90 90 01 c8");
1143 let [f] = &text.funcs[..] else { panic!("one function") };
1144 // The symbol is after the room in front of the label and its size counts none of it, which
1145 // is what makes a backtrace through the function name the function rather than the room.
1146 assert_eq!(f.start, 3);
1147 assert_eq!(f.len, 4);
1148 // And the record points at the front of the whole thing, which here is the front of the
1149 // function's bytes because there is room in front of the label.
1150 assert_eq!(f.patch, Some(Patch { at: 0, before: 3 }));
1151 }
1152
1153 /// The same when the room is all after the label, which is what one number asks for.
1154 #[test]
1155 fn room_that_is_all_after_the_label_is_recorded_where_it_really_starts() {
1156 let mut names = Interner::new();
1157 let mut func = Func::new(names.intern("f"));
1158 let block = func.create_block();
1159 // A landing pad in front of it, which is the one thing that goes between the label and the
1160 // room and is why the record is not just the top of the function.
1161 let landing = Opcode::new(names.intern("x64.endbr64"));
1162 func.build(block, landing).finish();
1163 let pad = Opcode::new(names.intern("x64.nop"));
1164 let first = func.build(block, pad).finish();
1165 func.build(block, pad).finish();
1166 add(&mut func, &mut names);
1167 func.patch = Some(rucc_mir::Patch { before: 0, pad, after: Some(first) });
1168
1169 let text = assemble(&[func], &names, &target(), true, false)
1170 .expect("a function with room in it")
1171 .text;
1172 assert_eq!(hex(&text.bytes), "f3 0f 1e fa 90 90 01 c8");
1173 let [f] = &text.funcs[..] else { panic!("one function") };
1174 assert_eq!(f.start, 0);
1175 assert_eq!(f.patch, Some(Patch { at: 4, before: 0 }));
1176 }
1177
1178 #[test]
1179 fn a_global_read_out_of_the_offset_table_asks_for_the_relocation_that_names_the_slot() {
1180 let mut names = Interner::new();
1181 let mut func = Func::new(names.intern("f"));
1182 let block = func.create_block();
1183 let load = Opcode::new(names.intern("x64.mov_rm_64"));
1184 let away = names.intern("away");
1185 func.build(block, load)
1186 .operand(Operand::write(Reg::physical(RAX), GPR))
1187 .mem(Mem::got(away))
1188 .finish();
1189
1190 let text = assemble(&[func], &names, &target(), true, false)
1191 .expect("a load through the offset table")
1192 .text;
1193 // A `mov` with a REX prefix, which the relocation requires by name: the linker is allowed
1194 // to turn it back into a `lea`, and it can only do that when it knows what it is looking
1195 // at down to the prefix.
1196 assert_eq!(hex(&text.bytes), "48 8b 05 00 00 00 00");
1197 assert_eq!(
1198 text.relocs,
1199 [Reloc {
1200 at: 3,
1201 symbol: "away".to_owned(),
1202 kind: Reference::Got,
1203 addend: -4,
1204 after: 0
1205 }]
1206 );
1207 }
1208
1209 #[test]
1210 fn an_address_that_names_a_register_is_not_a_relocation() {
1211 let text = write(|func, names| {
1212 let block = func.create_block();
1213 let lea = Opcode::new(names.intern("x64.lea_64"));
1214 func.build(block, lea)
1215 .operand(Operand::write(Reg::physical(RAX), GPR))
1216 .mem(
1217 Mem::at(Operand::read(Reg::physical(RCX), GPR))
1218 .indexed(Operand::read(Reg::physical(RDX), GPR), 4)
1219 .plus(-16),
1220 )
1221 .finish();
1222 });
1223 assert_eq!(hex(&text.bytes), "48 8d 44 91 f0");
1224 assert!(text.relocs.is_empty());
1225 }
1226
1227 #[test]
1228 fn every_function_starts_on_a_boundary_and_the_space_in_front_of_one_does_nothing() {
1229 let mut names = Interner::new();
1230 let mut first = Func::new(names.intern("f"));
1231 add(&mut first, &mut names);
1232 let mut second = Func::new(names.intern("g"));
1233 add(&mut second, &mut names);
1234
1235 let text =
1236 assemble(&[first, second], &names, &target(), true, false).expect("two functions").text;
1237 assert_eq!(text.funcs[1].start, 16);
1238 assert_eq!(text.bytes.len(), 18);
1239 assert!(text.bytes[2..16].iter().all(|byte| *byte == NOP), "{:?}", text.bytes);
1240 }
1241
1242 #[test]
1243 fn a_function_that_was_never_allocated_is_refused_rather_than_encoded_wrongly() {
1244 let mut names = Interner::new();
1245 let mut func = Func::new(names.intern("f"));
1246 let block = func.create_block();
1247 let vreg = func.new_vreg(GPR);
1248 let neg = Opcode::new(names.intern("x64.neg_r_32"));
1249 func.build(block, neg).operand(Operand::write(vreg, GPR)).finish();
1250 let error =
1251 assemble(&[func], &names, &target(), true, false).expect_err("a virtual register");
1252 assert_eq!(
1253 error,
1254 Error::Virtual { func: "f".to_owned(), opcode: "x64.neg_r_32".to_owned() }
1255 );
1256 }
1257
1258 #[test]
1259 fn an_opcode_the_target_does_not_describe_is_refused() {
1260 let mut names = Interner::new();
1261 let mut func = Func::new(names.intern("f"));
1262 let block = func.create_block();
1263 let made_up = Opcode::new(names.intern("x64.frobnicate"));
1264 func.build(block, made_up).finish();
1265 let error =
1266 assemble(&[func], &names, &target(), true, false).expect_err("no such instruction");
1267 assert_eq!(
1268 error,
1269 Error::Opcode { func: "f".to_owned(), opcode: "x64.frobnicate".to_owned() }
1270 );
1271 }
1272
1273 #[test]
1274 fn a_build_that_asked_for_debug_information_is_told_where_each_instruction_began() {
1275 let mut names = Interner::new();
1276 let mut func = Func::new(names.intern("f"));
1277 let block = func.create_block();
1278 let add = Opcode::new(names.intern("x64.add_rr_32"));
1279 for at in 0..2u32 {
1280 func.build(block, add)
1281 .at(Span::new(at * 10, at * 10 + 3))
1282 .operand(Operand::write(Reg::physical(RAX), GPR))
1283 .operand(Operand::read(Reg::physical(RAX), GPR))
1284 .operand(Operand::read(Reg::physical(RCX), GPR))
1285 .finish();
1286 }
1287
1288 // And which instruction each row is for, which the line table has no use for and the
1289 // locations do, since a stretch a local is somewhere over is named by an instruction at
1290 // each end and this is where one gets an address.
1291 let line: Vec<Inst> = func.blocks().flat_map(|block| func.insts(block)).collect();
1292 let out = assemble(&[func], &names, &target(), true, true).expect("two instructions");
1293 assert_eq!(
1294 out.lines,
1295 vec![vec![
1296 Row { at: 0, span: Span::new(0, 3), inst: Some(line[0]) },
1297 Row { at: 2, span: Span::new(10, 13), inst: Some(line[1]) },
1298 ]]
1299 );
1300 }
1301
1302 #[test]
1303 fn a_function_that_knows_where_it_was_declared_says_so_over_its_prologue() {
1304 // The front of a function is instructions no expression in the source asked for, so
1305 // nothing there carries a span and the bytes would be covered by nothing. The declaration
1306 // is what gcc puts over them and it is what this puts over them too, as a row at zero in
1307 // front of everything the body produced.
1308 let mut names = Interner::new();
1309 let mut func = Func::new(names.intern("f"));
1310 func.declared = Span::new(100, 104);
1311 let block = func.create_block();
1312 let add = Opcode::new(names.intern("x64.add_rr_32"));
1313 // The first with no span, the way every instruction a prologue is made of has none, and
1314 // the second with one, the way an instruction the body asked for does.
1315 for span in [Span::DUMMY, Span::new(10, 13)] {
1316 func.build(block, add)
1317 .at(span)
1318 .operand(Operand::write(Reg::physical(RAX), GPR))
1319 .operand(Operand::read(Reg::physical(RAX), GPR))
1320 .operand(Operand::read(Reg::physical(RCX), GPR))
1321 .finish();
1322 }
1323
1324 // The row for the declaration is the one row here no instruction wrote, which is what
1325 // says the bytes it covers are the prologue's.
1326 let line: Vec<Inst> = func.blocks().flat_map(|block| func.insts(block)).collect();
1327 let out = assemble(&[func], &names, &target(), true, true).expect("two instructions");
1328 assert_eq!(
1329 out.lines,
1330 vec![vec![
1331 Row { at: 0, span: Span::new(100, 104), inst: None },
1332 Row { at: 0, span: Span::DUMMY, inst: Some(line[0]) },
1333 Row { at: 2, span: Span::new(10, 13), inst: Some(line[1]) },
1334 ]]
1335 );
1336 }
1337
1338 #[test]
1339 fn a_build_that_asked_for_none_carries_no_rows_at_all() {
1340 let mut names = Interner::new();
1341 let mut func = Func::new(names.intern("f"));
1342 add(&mut func, &mut names);
1343
1344 let out = assemble(&[func], &names, &target(), true, false).expect("one instruction");
1345 assert_eq!(out.lines, vec![Vec::new()]);
1346 }
1347
1348 #[test]
1349 fn a_machine_with_no_encoder_here_is_said_so_rather_than_encoded_as_x86_64() {
1350 let names = Interner::new();
1351 let aarch64 = TargetInfo::new(Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu));
1352 let error = assemble(&[], &names, &aarch64, true, false).expect_err("no encoder");
1353 assert!(matches!(error, Error::Machine { .. }), "{error:?}");
1354 }
1355
1356 /// A loop that would cross a line starts on the next one, the gap is instructions that do
1357 /// nothing, and a loop that fits where it falls is left there.
1358 #[test]
1359 fn the_head_of_a_loop_that_would_cross_a_line_starts_on_the_next_one() {
1360 let laid = |ahead: usize| {
1361 write(|func, names| {
1362 let first = func.create_block();
1363 let head = func.create_block();
1364 let add = Opcode::new(names.intern("x64.add_rr_32"));
1365 for block in std::iter::repeat_n(first, ahead).chain(std::iter::repeat_n(head, 15))
1366 {
1367 func.build(block, add)
1368 .operand(Operand::write(Reg::physical(RAX), GPR))
1369 .operand(Operand::read(Reg::physical(RAX), GPR))
1370 .operand(Operand::read(Reg::physical(RCX), GPR))
1371 .finish();
1372 }
1373 func.build(head, Opcode::new(names.intern("x64.jmp"))).finish();
1374 func.succs_mut(head).push(BlockCall::to(head));
1375 func.heads = vec![head];
1376 })
1377 };
1378 // Fifteen adds and the five byte jump back are a loop of thirty five bytes. Twenty adds in
1379 // front put it at forty, which crosses at sixty four, so it moves there.
1380 let text = laid(20);
1381 assert_eq!(text.bytes.len(), 64 + 35);
1382 assert_eq!(hex(&text.bytes[64..66]), "01 c8");
1383 assert!(text.bytes[40..64].iter().all(|&byte| byte != 0x01), "only padding in the gap");
1384 assert_eq!(text.bytes[40], 0x66, "a long nop rather than single bytes");
1385 assert!(text.align >= 64, "{}", text.align);
1386 // Ten adds in front put it at twenty, and it ends at fifty five without crossing.
1387 let text = laid(10);
1388 assert_eq!(text.bytes.len(), 20 + 35);
1389 assert_eq!(hex(&text.bytes[20..22]), "01 c8");
1390 }
1391}