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