Skip to main content

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.2")]
45
46mod att;
47mod bytes;
48mod data;
49mod format;
50mod instruction;
51mod source;
52mod unwind;
53
54pub use crate::att::print;
55pub use crate::bytes::{Assembled, Row, assemble};
56pub use crate::data::{Globals, Piece, Variable, aliases, globals};
57pub use crate::format::Directives;
58pub use crate::source::{Trouble, read};
59
60use std::fmt;
61
62use rucc_base::Interner;
63use rucc_mir::Func;
64use rucc_target::x86_64;
65
66/// Whether any of these functions holds a template kept as text, which is what decides that the
67/// unit is assembled from its listing rather than written out as bytes directly. See
68/// [`x86_64::Form::Template`].
69#[must_use]
70pub fn kept(funcs: &[Func], names: &Interner) -> bool {
71    let wanted = format!("x64.{}", x86_64::TEMPLATE);
72    funcs.iter().any(|func| {
73        func.blocks().any(|block| {
74            func.insts(block).any(|inst| names.resolve(func[inst].opcode.name()) == wanted)
75        })
76    })
77}
78
79/// The milestone in `spec/17-milestones.md` that fills this crate in.
80pub const MILESTONE: &str = "M3";
81
82/// A function this compiler could not write out as assembly.
83///
84/// Neither of these is a program's fault and neither should ever reach a user, since a machine
85/// function that reaches here has been through the whole backend and the tests pin both of the
86/// claims below. They are errors rather than assertions because the alternative to reporting one
87/// is writing a listing that is quietly wrong, and a wrong listing is the failure section 11.1 is
88/// written to prevent.
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub enum Error {
91    /// An opcode the target has no description of.
92    Opcode {
93        /// The function it turned up in.
94        func: String,
95        /// The opcode, as the machine IR spells it.
96        opcode: String,
97    },
98    /// A register that is still virtual, which is a function that was never allocated.
99    Virtual {
100        /// The function it turned up in.
101        func: String,
102        /// The opcode the register is an operand of.
103        opcode: String,
104    },
105    /// An instruction the description names and the encoder could not write bytes for.
106    ///
107    /// The two halves of the description are meant to hold the same instructions, and a test
108    /// pins that they do, so this is either a row that was left out of one of them or an
109    /// operand the machine cannot express in the instruction that was chosen for it.
110    Encode {
111        /// The function it turned up in.
112        func: String,
113        /// The opcode, as the machine IR spells it.
114        opcode: String,
115        /// What the encoder said, already formatted.
116        why: String,
117    },
118    /// A jump inside a function to somewhere more than two gigabytes away.
119    ///
120    /// A single function that long is not a program anybody wrote, and the four bytes a jump
121    /// carries are all there are, so this is reported rather than wrapped around into a jump
122    /// somewhere else entirely.
123    Distance {
124        /// The function it turned up in.
125        func: String,
126        /// How far the jump would have had to reach.
127        bytes: i64,
128    },
129    /// A machine this crate cannot write assembly for.
130    Machine {
131        /// The triple that was asked for.
132        triple: String,
133    },
134    /// A thread-local variable on a format that does not spell one the way ELF does.
135    ///
136    /// The only one of these that is about a program rather than about this compiler. ELF says a
137    /// thread-local variable with a section flag and a symbol type, and that is written. Windows
138    /// hands out an index at load time and reaches the variable through a table the index names,
139    /// and Mach-O puts a descriptor in front of every one and reaches it by calling through the
140    /// descriptor, so on those two one is refused rather than written out as an ordinary variable
141    /// that every thread would share.
142    Thread {
143        /// The variable, as the C program spelled it.
144        name: String,
145        /// The object format that has no writing of one here, as its own name.
146        format: &'static str,
147    },
148    /// An ifunc, which is not a mistake and not written yet.
149    ///
150    /// The other thing an alias in the IR can be, and a different job from a second name for
151    /// something: the symbol is resolved once at program start by calling a function in this
152    /// object, which wants a symbol type of its own and a relocation of its own. One is refused
153    /// rather than written as an ordinary alias that would go to the resolver instead of to what
154    /// the resolver picked.
155    IFunc {
156        /// The name it defines, as the C program spelled it.
157        name: String,
158    },
159    /// A prologue the target's unwind table has no way to describe.
160    ///
161    /// ELF carries a little program per function and can say anything an instruction did to the
162    /// frame. Windows carries a fixed list of codes instead, each one of a handful of shapes a
163    /// prologue is allowed to have, and a prologue outside that list has no spelling there. The
164    /// one this compiler writes that does not fit is the frame pointer form, which establishes the
165    /// pointer before it takes the frame, so the table is refused rather than written describing a
166    /// frame of the wrong size. A build that does not want a table at all is the way past it, which
167    /// is `-fno-asynchronous-unwind-tables -fno-unwind-tables`.
168    Frame {
169        /// The function it turned up in.
170        func: String,
171        /// What about its prologue, already formatted.
172        why: String,
173    },
174    /// A piece of an initializer nothing here can write down.
175    Image {
176        /// The variable it is part of.
177        name: String,
178        /// What about it, already formatted.
179        why: String,
180    },
181}
182
183impl fmt::Display for Error {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        match self {
186            Error::Opcode { func, opcode } => {
187                write!(f, "'{func}' has a '{opcode}' and the target does not say what one is")
188            }
189            Error::Virtual { func, opcode } => {
190                write!(f, "'{func}' reached the assembler with a virtual register in a '{opcode}'")
191            }
192            Error::Encode { func, opcode, why } => {
193                write!(f, "'{func}' has a '{opcode}' the encoder refused: {why}")
194            }
195            Error::Distance { func, bytes } => {
196                write!(f, "'{func}' has a jump reaching {bytes} bytes, which does not fit in four")
197            }
198            Error::Machine { triple } => {
199                write!(f, "there is no assembly writer for {triple} in this compiler yet")
200            }
201            Error::Thread { name, format } => {
202                write!(f, "'{name}' is thread-local, which is not written on {format} yet")
203            }
204            Error::IFunc { name } => {
205                write!(f, "'{name}' is an ifunc, which this compiler does not write yet")
206            }
207            Error::Frame { func, why } => {
208                write!(f, "'{func}' has {why}, which no unwind table here can describe")
209            }
210            Error::Image { name, why } => {
211                write!(f, "the initializer of '{name}' has {why} in it, which cannot be written")
212            }
213        }
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    #[test]
220    fn milestone_is_recorded() {
221        assert!(super::MILESTONE.starts_with('M'));
222    }
223}