Skip to main content

cranelift_assembler_x64_meta/
dsl.rs

1//! Defines a domain-specific language (DSL) for describing x64 instructions.
2//!
3//! This language is intended to be:
4//! - compact--i.e., define an x64 instruction on a single line, and
5//! - a close-to-direct mapping of what we read in the x64 reference manual.
6
7mod custom;
8mod encoding;
9mod features;
10pub mod format;
11
12pub use custom::{Custom, Customization};
13pub use encoding::{Encoding, ModRmKind, OpcodeMod};
14pub use encoding::{Evex, Length, Vex, VexEscape, VexPrefix, evex, vex};
15pub use encoding::{
16    Group1Prefix, Group2Prefix, Group3Prefix, Group4Prefix, Opcodes, Prefixes, Rex, TupleType, rex,
17};
18pub use features::{ALL_FEATURES, Feature, Features};
19pub use format::{Eflags, Extension, Format, Location, Mutability, Operand, OperandKind, RegClass};
20pub use format::{align, fmt, implicit, r, rw, sxl, sxq, sxw, w};
21
22/// Abbreviated constructor for an x64 instruction.
23pub fn inst(
24    mnemonic: impl Into<String>,
25    format: Format,
26    encoding: impl Into<Encoding>,
27    features: impl Into<Features>,
28) -> Inst {
29    let encoding = encoding.into();
30    encoding.validate(&format.operands);
31    Inst {
32        mnemonic: mnemonic.into(),
33        format,
34        encoding,
35        features: features.into(),
36        alternate: None,
37        has_trap: false,
38        custom: Custom::default(),
39    }
40}
41
42/// An x64 instruction.
43///
44/// Use [`inst`] to construct this within the
45/// [`instructions`](super::instructions) module. This structure is designed to
46/// represent all of the information for one instruction (a table row) in the
47/// x64 _Instruction Set Reference_ or at least enough to generate code to emit
48/// the instruction.
49pub struct Inst {
50    /// The instruction name as represented in the x64 reference manual. This is
51    /// the pretty-printed name used for disassembly. Multiple instructions may
52    /// have the same mnemonic, though; the combination of this field and the
53    /// format name must be unique (see [`Inst::name`]).
54    pub mnemonic: String,
55    /// The instruction operands, typically represented in the "Instruction"
56    /// column of the x64 reference manual.
57    pub format: Format,
58    /// The instruction encoding, typically represented in the "Opcode" column
59    /// of the x64 reference manual.
60    pub encoding: Encoding,
61    /// The CPU features required to use this instruction; this combines the
62    /// "64-bit/32-bit Mode Support" and "CPUID Feature Flag" columns of the x64
63    /// reference manual.
64    pub features: Features,
65    /// An alternate version of this instruction, if it exists.
66    pub alternate: Option<Alternate>,
67    /// Whether or not this instruction can trap and thus needs a `TrapCode`
68    /// payload in the instruction itself.
69    pub has_trap: bool,
70    /// Whether or not this instruction uses custom, external functions
71    /// instead of Rust code generated by this crate.
72    pub custom: Custom,
73}
74
75impl Inst {
76    /// The unique name for this instruction.
77    ///
78    /// To avoid ambiguity, this name combines the instruction mnemonic and the
79    /// format name in snake case. This is used in generated code to name the
80    /// instruction `struct` and builder functions.
81    ///
82    /// In rare cases, this `<mnemonic>_<format>` scheme does not uniquely
83    /// identify an instruction in x64 ISA (e.g., some extended versions,
84    /// VEX/EVEX). In these cases, we append a minimal identifier to
85    /// the format name (e.g., `sx*`) to keep this unique.
86    #[must_use]
87    pub fn name(&self) -> String {
88        format!(
89            "{}_{}",
90            self.mnemonic.to_lowercase(),
91            self.format.name.to_lowercase()
92        )
93    }
94
95    /// Flags this instruction as being able to trap, so needs a `TrapCode` at
96    /// compile time to track this.
97    pub fn has_trap(mut self) -> Self {
98        self.has_trap = true;
99        self
100    }
101
102    /// Indicate this instruction as needing custom processing.
103    pub fn custom(mut self, custom: impl Into<Custom>) -> Self {
104        self.custom = custom.into();
105        self
106    }
107
108    /// Sets the alternate version of this instruction, if it exists.
109    pub fn alt(mut self, feature: Feature, alternate: impl Into<String>) -> Self {
110        self.alternate = Some(Alternate {
111            feature,
112            name: alternate.into(),
113        });
114        self
115    }
116
117    /// The mnemonics XED prints for this encoding: `(register form, memory
118    /// form)`. XED picks a different spelling than we do in three systematic
119    /// ways, all handled here:
120    ///
121    /// 1. it drops the AT&T operand-size suffix we carry (`addl` -> `add`);
122    /// 2. it prefers a different condition-code alias (`cmovae` -> `cmovnb`);
123    /// 3. it appends a width marker when an operand is *memory*, sized by that
124    ///    operand (`addsd` -> `addsdq`, `cmovnb` -> `cmovnbl`).
125    ///
126    /// The two forms differ only when (3) applies, which is why this returns a
127    /// pair: the choice is made at runtime from the `r/m` operand.
128    #[must_use]
129    pub fn xed_mnemonics(&self) -> (String, String) {
130        // `lock_addb` is printed `lock addb`; see `custom::mnemonic`.
131        let mut base = match self.mnemonic.strip_prefix("lock_") {
132            Some(rest) => format!("lock {rest}"),
133            None => self.mnemonic.clone(),
134        };
135
136        // (1) Drop our size suffix when it matches the width of some operand.
137        // Mnemonics XED spells differently outright keep their suffix (it is
138        // part of the name), as do condition-code mnemonics whose trailing
139        // letter is the condition itself (`setb`) rather than a size.
140        if !is_exception(&base) && !is_condition_family(&base) && !keeps_suffix(&base, &self.format)
141        {
142            let widths: Vec<_> = self
143                .format
144                .locations()
145                .map(|l| width_marker(l.bits()))
146                .collect();
147            if base.len() > 2 && widths.iter().any(|w| base.ends_with(w)) {
148                base.truncate(base.len() - 1);
149            }
150        }
151
152        // (2) XED's preferred spelling for the families that have aliases.
153        base = rename(&base);
154
155        // (3) A width marker is appended when the operand is memory, sized by
156        // that operand. `lea` names an address rather than accessing it, so
157        // never takes one; `push`/`pop` always do, even for registers; and for
158        // mnemonics that already name their width the marker repeats it
159        // (`pextrb` -> `pextrbb`).
160        if matches!(base.as_str(), "push" | "pop") {
161            let bits = self.format.locations().next().map_or(64, Location::bits);
162            let m = format!("{base}{}", width_marker(bits));
163            return (m.clone(), m);
164        }
165        let mem = match self.format.uses_memory() {
166            Some(_) if base == "lea" => base.clone(),
167            Some(_) if keeps_suffix(&base, &self.format) => {
168                // The mnemonic already names its width; XED repeats it using
169                // its own letter (`pextrd` is 32-bit, so `pextrdl`).
170                let named = match &base[base.len() - 1..] {
171                    "b" => 8,
172                    "w" => 16,
173                    "d" => 32,
174                    _ => 64,
175                };
176                format!("{base}{}", width_marker(named))
177            }
178            Some(loc) => format!("{base}{}", width_marker(loc.bits())),
179            None => base.clone(),
180        };
181        (base, mem)
182    }
183}
184
185/// XED's single-letter marker for an operand width.
186fn width_marker(bits: u16) -> &'static str {
187    match bits {
188        8 => "b",
189        16 => "w",
190        32 => "l",
191        64 => "q",
192        128 => "x",
193        256 => "y",
194        512 => "z",
195        b => unreachable!("no XED width marker for {b} bits"),
196    }
197}
198
199/// Mnemonics XED spells differently outright.
200const XED_RENAMES: &[(&str, &str)] = &[
201    ("cbtw", "data16 cbw"),
202    ("cltd", "cdq"),
203    ("cltq", "cdqe"),
204    ("cqto", "cqo"),
205    ("cwtd", "data16 cwd"),
206    ("cwtl", "cwde"),
207    ("movabsq", "mov"),
208    ("movslq", "movsxd"),
209];
210
211fn is_exception(m: &str) -> bool {
212    XED_RENAMES.iter().any(|(o, _)| *o == m)
213}
214
215/// The sixteen x64 condition codes, as spelled in our mnemonics.
216const CONDITIONS: &[&str] = &[
217    "a", "ae", "b", "be", "e", "g", "ge", "l", "le", "ne", "no", "np", "ns", "o", "p", "s",
218];
219
220/// Whether `m` is a `cmov`/`set`/`j` mnemonic carrying a valid condition code.
221fn is_condition_family(m: &str) -> bool {
222    ["cmov", "set", "j"]
223        .iter()
224        .filter_map(|p| m.strip_prefix(p))
225        .any(|cc| CONDITIONS.contains(&cc))
226}
227
228/// Mnemonics whose trailing size letter names the instruction (`pextrb`
229/// extracts a *byte*) rather than decorating it in the AT&T style.
230fn keeps_suffix(m: &str, format: &Format) -> bool {
231    const INTRINSIC: &str =
232        "pextr pinsr vpextr vpinsr vpbroadcast pmovsxd pmovzxd vpmovsxd vpmovzxd";
233    INTRINSIC.split(' ').any(|p| m.starts_with(p))
234        // `movq` is a GPR move (suffix) or an XMM move (name) by operand type.
235        || (matches!(m, "movq" | "vmovq") && format.locations().any(|l| l.bits() == 128))
236}
237
238/// Rewrite a suffix-stripped mnemonic into XED's preferred spelling.
239fn rename(base: &str) -> String {
240    if let Some((_, x)) = XED_RENAMES.iter().find(|(o, _)| *o == base) {
241        return (*x).to_string();
242    }
243
244    // `movzbl`/`movswq`/... collapse to plain `movzx`/`movsx`; after the size
245    // suffix is stripped a single source-width letter remains.
246    for (prefix, xed) in [("movz", "movzx"), ("movs", "movsx")] {
247        if let Some(rest) = base.strip_prefix(prefix) {
248            if rest.len() == 1 && "bwlq".contains(rest) {
249                return xed.to_string();
250            }
251        }
252    }
253
254    // Condition-code families: XED prefers the negated spelling for six of the
255    // sixteen conditions.
256    for prefix in ["cmov", "set", "j"] {
257        if let Some(cc) = base.strip_prefix(prefix) {
258            let renamed = match cc {
259                "a" => "nbe",
260                "ae" => "nb",
261                "e" => "z",
262                "g" => "nle",
263                "ge" => "nl",
264                "ne" => "nz",
265                _ => return base.to_string(),
266            };
267            return format!("{prefix}{renamed}");
268        }
269    }
270
271    base.to_string()
272}
273
274impl core::fmt::Display for Inst {
275    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
276        let Inst {
277            mnemonic: name,
278            format,
279            encoding,
280            features,
281            alternate,
282            has_trap,
283            custom,
284        } = self;
285        write!(f, "{name}: {format} => {encoding}")?;
286        write!(f, " [{features}]")?;
287        if let Some(alternate) = alternate {
288            write!(f, " (alternate: {alternate})")?;
289        }
290        if *has_trap {
291            write!(f, " has_trap")?;
292        }
293        if !custom.is_empty() {
294            write!(f, " custom({custom})")?;
295        }
296        Ok(())
297    }
298}
299
300/// An alternate version of an instruction.
301///
302/// Some AVX-specific context: some instructions have the same semantics in
303/// their SSE and AVX encodings. In these cases, we use this structure to record
304/// the name of the upgraded version of the instruction, allowing us to replace
305/// the SSE instruction with its AVX version during lowering. For AVX, using the
306/// VEX-encoded instruction is typically better than its legacy SSE version:
307/// - VEX can encode three operands
308/// - VEX allows unaligned memory access (avoids additional `MOVUPS`)
309/// - VEX can compact byte-long prefixes into the VEX prefix
310/// - VEX instructions zero the upper bits of XMM registers by default
311pub struct Alternate {
312    /// Indicate the feature check to use to trigger the replacement.
313    pub feature: Feature,
314    /// The full name (see [`Inst::name`]) of the instruction used for
315    /// replacement.
316    pub name: String,
317}
318
319impl core::fmt::Display for Alternate {
320    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
321        write!(f, "{} => {}", self.feature, self.name)
322    }
323}