rucc_asm/lib.rs
1//! Instruction encoders, the integrated assembler, inline assembly and relaxation.
2//!
3//! Design: `spec/11-asm-objects-debug.md`. Layer rank 11, see `spec/18-package-layout.md`.
4//!
5//! # Status
6//!
7//! What is written are the two things a compiler does with a machine function: the assembly text
8//! `-S` produces, which is [`print()`], and the bytes of a text section, which is [`assemble`].
9//! Section 11.1 asks for one instruction description behind both, and there is one: the walk over
10//! a function is the same walk in both files, reading the same list out of `rucc-target`, and the
11//! only difference is whether an instruction is written down by name or handed to the encoder. So
12//! the listing and the object file cannot come to disagree about what an instruction is.
13//!
14//! What [`assemble`] hands back with the bytes is what the linker has to be told: where each
15//! function starts and how long it is, and every place in the bytes that names something this
16//! file does not contain. The jumps inside a function are not among them, because by the end of a
17//! function every block has a place and they are filled in here.
18//!
19//! A build that asked for debug information gets one more thing: where each machine instruction
20//! began and which span of the source it came from. Spans rather than files and lines, because this
21//! layer has no source map and the thing that has one is the driver, which is also the only place a
22//! `-ffile-prefix-map` still has paths to rewrite.
23//!
24//! The variables a file defines are here for the same reason and in the same shape. [`globals`] is
25//! the one walk over a module's globals, and what it gives back is a list of pieces that
26//! [`print()`] writes down as directives and [`Globals::image`] writes down as bytes, so a `.long`
27//! in a listing and the four bytes in the object beside it cannot come to disagree either. Where a
28//! variable goes is worked out there rather than named by the front end, and what a section is
29//! called is the object format's business.
30//!
31//! [`read`] is the other direction: a file of assembly that somebody else wrote, turned into the
32//! sections and names an object is written from. The directives and the labels are one half of it
33//! and the instructions are the other, and a mnemonic with no bytes behind it is refused by name
34//! with its line number rather than skipped. Nothing there describes the machine a second time:
35//! the bytes of an instruction come from the one encoder in `rucc-target` that the compiler's own
36//! output goes through, so a file this assembles and a file this compiles cannot disagree about
37//! what an instruction is. Branch relaxation is not here yet, so a jump is four bytes of distance
38//! whether it needs them or not, which is correct and longer than gas would have written.
39//!
40//! Every crate in the workspace is published, and publishing implies a promise. This one is
41//! tier 3: its Rust API is explicitly unstable and will change without a major version bump.
42//! Depend on the `rucc` binary's behaviour, not on this.
43
44#![doc(html_root_url = "https://docs.rs/rucc-asm/0.11.7")]
45
46mod a64;
47mod att;
48mod bytes;
49mod data;
50mod format;
51mod instruction;
52mod source;
53mod unwind;
54
55pub use crate::att::print;
56pub use crate::bytes::{Assembled, Row, assemble};
57pub use crate::data::{Globals, Piece, Variable, aliases, globals};
58pub use crate::format::Directives;
59pub use crate::source::{Trouble, read};
60
61use std::fmt;
62
63use rucc_base::Interner;
64use rucc_mir::Func;
65use rucc_target::{TargetInfo, x86_64};
66
67/// Whether any of these functions holds a template kept as text that the assembler cannot read on
68/// its own, which is what decides that the unit is assembled from its listing rather than written
69/// out as bytes directly. See [`x86_64::Form::Template`].
70///
71/// One that jumps to a label another template defines, switches section or aligns what follows is
72/// only right in the listing, where every template is read as part of one file. Every other one is
73/// read by itself where it is and laid down as bytes, which keeps the line table and the rest of
74/// what `-g` writes, since those come out of [`assemble`] and not out of a listing.
75#[must_use]
76pub fn kept(funcs: &[Func], names: &Interner, target: &TargetInfo) -> bool {
77 let wanted = format!("x64.{}", x86_64::TEMPLATE);
78 let directives = Directives::of(target.object_format);
79 funcs.iter().any(|func| {
80 func.blocks().any(|block| {
81 func.insts(block).any(|inst| {
82 names.resolve(func[inst].opcode.name()) == wanted
83 && bytes::template(func, block, inst, names, directives).is_err()
84 })
85 })
86 })
87}
88
89/// The line a hot loop is kept inside, which is the cache line and the fetch block on the x86-64
90/// machines this was measured on.
91///
92/// Where a small loop starts matters on those machines only as far as whether it crosses one of
93/// these. The same thirty eight bytes of loop ran in 527M to 553M cycles wherever it fitted inside
94/// one line and in 578M to 753M wherever it crossed, while gcc's rule of sixteen bytes when that is
95/// near and eight otherwise kept it inside a line only half the time. That is `tamnd/rucc#1838`.
96const LINE: usize = 64;
97
98/// The most padding one loop is given, whatever it would take to keep it inside a line.
99///
100/// Half a line. With no limit the padding cost SQLite 1.01% of its text, with this one 0.48%, and
101/// with a quarter of a line 0.16%, which is too little to reach the loop the rule was written for:
102/// its head was twenty bytes short of the next line.
103const MOST_PADDING: usize = 31;
104
105/// The most padding worth putting in front of a loop that is `size` bytes from its head to the end
106/// of the jump back to it, or nothing when no padding would keep it inside a line.
107///
108/// A loop longer than a line crosses one wherever it starts. A loop of one line or less crosses one
109/// exactly when the padding to the next line is less than its size, so asking for the next line
110/// with that much padding at most pads the loops that cross and leaves the ones that do not alone.
111/// That is gas's `.p2align 6,,N`, which is what the listing writes, and [`loop_padding`] is the
112/// same arithmetic for the object writer. The two are beside each other so that they are changed
113/// together.
114fn loop_room(size: usize) -> Option<usize> {
115 (size > 1 && size <= LINE).then(|| (size - 1).min(MOST_PADDING))
116}
117
118/// How many bytes of padding go in front of the head of a loop of that size that would otherwise
119/// start `at` bytes into the section. See [`loop_room`].
120fn loop_padding(at: usize, size: usize) -> usize {
121 let Some(most) = loop_room(size) else { return 0 };
122 let wanted = at.next_multiple_of(LINE) - at;
123 if wanted <= most { wanted } else { 0 }
124}
125
126/// The milestone in `spec/17-milestones.md` that fills this crate in.
127pub const MILESTONE: &str = "M3";
128
129/// A function this compiler could not write out as assembly.
130///
131/// Neither of these is a program's fault and neither should ever reach a user, since a machine
132/// function that reaches here has been through the whole backend and the tests pin both of the
133/// claims below. They are errors rather than assertions because the alternative to reporting one
134/// is writing a listing that is quietly wrong, and a wrong listing is the failure section 11.1 is
135/// written to prevent.
136#[derive(Debug, Clone, PartialEq, Eq)]
137pub enum Error {
138 /// An opcode the target has no description of.
139 Opcode {
140 /// The function it turned up in.
141 func: String,
142 /// The opcode, as the machine IR spells it.
143 opcode: String,
144 },
145 /// A register that is still virtual, which is a function that was never allocated.
146 Virtual {
147 /// The function it turned up in.
148 func: String,
149 /// The opcode the register is an operand of.
150 opcode: String,
151 },
152 /// An instruction the description names and the encoder could not write bytes for.
153 ///
154 /// The two halves of the description are meant to hold the same instructions, and a test
155 /// pins that they do, so this is either a row that was left out of one of them or an
156 /// operand the machine cannot express in the instruction that was chosen for it.
157 Encode {
158 /// The function it turned up in.
159 func: String,
160 /// The opcode, as the machine IR spells it.
161 opcode: String,
162 /// What the encoder said, already formatted.
163 why: String,
164 },
165 /// A jump inside a function to somewhere more than two gigabytes away.
166 ///
167 /// A single function that long is not a program anybody wrote, and the four bytes a jump
168 /// carries are all there are, so this is reported rather than wrapped around into a jump
169 /// somewhere else entirely.
170 Distance {
171 /// The function it turned up in.
172 func: String,
173 /// How far the jump would have had to reach.
174 bytes: i64,
175 },
176 /// A machine this crate cannot write assembly for.
177 Machine {
178 /// The triple that was asked for.
179 triple: String,
180 },
181 /// A thread-local variable on a format that does not spell one the way ELF does.
182 ///
183 /// The only one of these that is about a program rather than about this compiler. ELF says a
184 /// thread-local variable with a section flag and a symbol type, and that is written. Windows
185 /// hands out an index at load time and reaches the variable through a table the index names,
186 /// and Mach-O puts a descriptor in front of every one and reaches it by calling through the
187 /// descriptor, so on those two one is refused rather than written out as an ordinary variable
188 /// that every thread would share.
189 Thread {
190 /// The variable, as the C program spelled it.
191 name: String,
192 /// The object format that has no writing of one here, as its own name.
193 format: &'static str,
194 },
195 /// An ifunc, which is not a mistake and not written yet.
196 ///
197 /// The other thing an alias in the IR can be, and a different job from a second name for
198 /// something: the symbol is resolved once at program start by calling a function in this
199 /// object, which wants a symbol type of its own and a relocation of its own. One is refused
200 /// rather than written as an ordinary alias that would go to the resolver instead of to what
201 /// the resolver picked.
202 IFunc {
203 /// The name it defines, as the C program spelled it.
204 name: String,
205 },
206 /// A prologue the target's unwind table has no way to describe.
207 ///
208 /// ELF carries a little program per function and can say anything an instruction did to the
209 /// frame. Windows carries a fixed list of codes instead, each one of a handful of shapes a
210 /// prologue is allowed to have, and a prologue outside that list has no spelling there. The
211 /// one this compiler writes that does not fit is the frame pointer form, which establishes the
212 /// pointer before it takes the frame, so the table is refused rather than written describing a
213 /// frame of the wrong size. A build that does not want a table at all is the way past it, which
214 /// is `-fno-asynchronous-unwind-tables -fno-unwind-tables`.
215 Frame {
216 /// The function it turned up in.
217 func: String,
218 /// What about its prologue, already formatted.
219 why: String,
220 },
221 /// A piece of an initializer nothing here can write down.
222 Image {
223 /// The variable it is part of.
224 name: String,
225 /// What about it, already formatted.
226 why: String,
227 },
228}
229
230impl fmt::Display for Error {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 match self {
233 Error::Opcode { func, opcode } => {
234 write!(f, "'{func}' has a '{opcode}' and the target does not say what one is")
235 }
236 Error::Virtual { func, opcode } => {
237 write!(f, "'{func}' reached the assembler with a virtual register in a '{opcode}'")
238 }
239 Error::Encode { func, opcode, why } => {
240 write!(f, "'{func}' has a '{opcode}' the encoder refused: {why}")
241 }
242 Error::Distance { func, bytes } => {
243 write!(f, "'{func}' has a jump reaching {bytes} bytes, which does not fit in four")
244 }
245 Error::Machine { triple } => {
246 write!(f, "there is no assembly writer for {triple} in this compiler yet")
247 }
248 Error::Thread { name, format } => {
249 write!(f, "'{name}' is thread-local, which is not written on {format} yet")
250 }
251 Error::IFunc { name } => {
252 write!(f, "'{name}' is an ifunc, which this compiler does not write yet")
253 }
254 Error::Frame { func, why } => {
255 write!(f, "'{func}' has {why}, which no unwind table here can describe")
256 }
257 Error::Image { name, why } => {
258 write!(f, "the initializer of '{name}' has {why} in it, which cannot be written")
259 }
260 }
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 #[test]
267 fn milestone_is_recorded() {
268 assert!(super::MILESTONE.starts_with('M'));
269 }
270
271 /// A loop that would cross a line is moved to the start of the next one, and a loop that
272 /// would not, that is too long for any line to hold or that is too far from the next line, is
273 /// left where it is.
274 #[test]
275 fn a_loop_is_padded_only_when_that_keeps_it_inside_a_line() {
276 let cases = [
277 (0, 38, 0),
278 (26, 38, 0),
279 (27, 38, 0),
280 (40, 38, 24),
281 (56, 38, 8),
282 (63, 38, 1),
283 (64 + 40, 38, 24),
284 (33, 64, 31),
285 (32, 64, 0),
286 (1, 65, 0),
287 (10, 1, 0),
288 ];
289 for (at, size, padding) in cases {
290 assert_eq!(super::loop_padding(at, size), padding, "{size} bytes at {at}");
291 let start = at + padding;
292 if padding > 0 {
293 assert!(start % 64 + size <= 64, "{size} bytes at {at} still cross a line");
294 }
295 }
296 }
297}