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//! The variables a file defines are here for the same reason and in the same shape. [`globals`] is
20//! the one walk over a module's globals, and what it gives back is a list of pieces that
21//! [`print()`] writes down as directives and [`Globals::image`] writes down as bytes, so a `.long`
22//! in a listing and the four bytes in the object beside it cannot come to disagree either. Where a
23//! variable goes is worked out there rather than named by the front end, and what a section is
24//! called is the object format's business.
25//!
26//! The assembler that reads `.s` and `.S`, inline assembly and relaxation are the rest of M3 and
27//! M4 and are not here yet.
28//!
29//! Every crate in the workspace is published, and publishing implies a promise. This one is
30//! tier 3: its Rust API is explicitly unstable and will change without a major version bump.
31//! Depend on the `rucc` binary's behaviour, not on this.
32
33#![doc(html_root_url = "https://docs.rs/rucc-asm/0.10.7")]
34
35mod att;
36mod bytes;
37mod data;
38mod format;
39mod unwind;
40
41pub use crate::att::print;
42pub use crate::bytes::assemble;
43pub use crate::data::{Globals, Piece, Variable, aliases, globals};
44pub use crate::format::Directives;
45
46use std::fmt;
47
48/// The milestone in `spec/17-milestones.md` that fills this crate in.
49pub const MILESTONE: &str = "M3";
50
51/// A function this compiler could not write out as assembly.
52///
53/// Neither of these is a program's fault and neither should ever reach a user, since a machine
54/// function that reaches here has been through the whole backend and the tests pin both of the
55/// claims below. They are errors rather than assertions because the alternative to reporting one
56/// is writing a listing that is quietly wrong, and a wrong listing is the failure section 11.1 is
57/// written to prevent.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum Error {
60    /// An opcode the target has no description of.
61    Opcode {
62        /// The function it turned up in.
63        func: String,
64        /// The opcode, as the machine IR spells it.
65        opcode: String,
66    },
67    /// A register that is still virtual, which is a function that was never allocated.
68    Virtual {
69        /// The function it turned up in.
70        func: String,
71        /// The opcode the register is an operand of.
72        opcode: String,
73    },
74    /// An instruction the description names and the encoder could not write bytes for.
75    ///
76    /// The two halves of the description are meant to hold the same instructions, and a test
77    /// pins that they do, so this is either a row that was left out of one of them or an
78    /// operand the machine cannot express in the instruction that was chosen for it.
79    Encode {
80        /// The function it turned up in.
81        func: String,
82        /// The opcode, as the machine IR spells it.
83        opcode: String,
84        /// What the encoder said, already formatted.
85        why: String,
86    },
87    /// A jump inside a function to somewhere more than two gigabytes away.
88    ///
89    /// A single function that long is not a program anybody wrote, and the four bytes a jump
90    /// carries are all there are, so this is reported rather than wrapped around into a jump
91    /// somewhere else entirely.
92    Distance {
93        /// The function it turned up in.
94        func: String,
95        /// How far the jump would have had to reach.
96        bytes: i64,
97    },
98    /// A machine this crate cannot write assembly for.
99    Machine {
100        /// The triple that was asked for.
101        triple: String,
102    },
103    /// A thread-local variable, which is not a mistake and not written yet.
104    ///
105    /// The only one of these that is about a program rather than about this compiler. Reaching a
106    /// thread-local variable is a call or a load off the thread pointer depending on the model,
107    /// and none of that is built, so one is refused rather than written out as an ordinary
108    /// variable that every thread would share.
109    Thread {
110        /// The variable, as the C program spelled it.
111        name: String,
112    },
113    /// An ifunc, which is not a mistake and not written yet.
114    ///
115    /// The other thing an alias in the IR can be, and a different job from a second name for
116    /// something: the symbol is resolved once at program start by calling a function in this
117    /// object, which wants a symbol type of its own and a relocation of its own. One is refused
118    /// rather than written as an ordinary alias that would go to the resolver instead of to what
119    /// the resolver picked.
120    IFunc {
121        /// The name it defines, as the C program spelled it.
122        name: String,
123    },
124    /// A piece of an initializer nothing here can write down.
125    Image {
126        /// The variable it is part of.
127        name: String,
128        /// What about it, already formatted.
129        why: String,
130    },
131}
132
133impl fmt::Display for Error {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        match self {
136            Error::Opcode { func, opcode } => {
137                write!(f, "'{func}' has a '{opcode}' and the target does not say what one is")
138            }
139            Error::Virtual { func, opcode } => {
140                write!(f, "'{func}' reached the assembler with a virtual register in a '{opcode}'")
141            }
142            Error::Encode { func, opcode, why } => {
143                write!(f, "'{func}' has a '{opcode}' the encoder refused: {why}")
144            }
145            Error::Distance { func, bytes } => {
146                write!(f, "'{func}' has a jump reaching {bytes} bytes, which does not fit in four")
147            }
148            Error::Machine { triple } => {
149                write!(f, "there is no assembly writer for {triple} in this compiler yet")
150            }
151            Error::Thread { name } => {
152                write!(f, "'{name}' is thread-local, which this compiler does not build yet")
153            }
154            Error::IFunc { name } => {
155                write!(f, "'{name}' is an ifunc, which this compiler does not write yet")
156            }
157            Error::Image { name, why } => {
158                write!(f, "the initializer of '{name}' has {why} in it, which cannot be written")
159            }
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    #[test]
167    fn milestone_is_recorded() {
168        assert!(super::MILESTONE.starts_with('M'));
169    }
170}