cranelift_assembler_x64_meta/dsl/
format.rs

1//! A DSL for describing x64 instruction formats--the shape of the operands.
2//!
3//! Every instruction has a format that corresponds to its encoding's expected
4//! operands. The format is what allows us to generate code that accepts
5//! operands of the right type and check that the operands are used in the right
6//! way.
7//!
8//! The entry point for this module is [`fmt`].
9//!
10//! ```
11//! # use cranelift_assembler_x64_meta::dsl::{fmt, rw, r, Location::*};
12//! let f = fmt("rm", [rw(r32), r(rm32)]);
13//! assert_eq!(f.to_string(), "rm(r32[rw], rm32)")
14//! ```
15
16/// An abbreviated constructor for an instruction "format."
17///
18/// These model what the reference manual calls "instruction operand encodings,"
19/// usually defined in a table after an instruction's opcodes.
20pub fn fmt(name: impl Into<String>, operands: impl IntoIterator<Item = impl Into<Operand>>) -> Format {
21    Format {
22        name: name.into(),
23        operands: operands.into_iter().map(Into::into).collect(),
24    }
25}
26
27/// An abbreviated constructor for a "read-write" operand.
28///
29/// # Panics
30///
31/// This function panics if the location is an immediate (i.e., an immediate
32/// cannot be written to).
33#[must_use]
34pub fn rw(location: Location) -> Operand {
35    assert!(!matches!(location.kind(), OperandKind::Imm(_)));
36    Operand {
37        location,
38        mutability: Mutability::ReadWrite,
39        extension: Extension::default(),
40    }
41}
42
43/// An abbreviated constructor for a "read" operand.
44#[must_use]
45pub fn r(location: Location) -> Operand {
46    Operand {
47        location,
48        mutability: Mutability::Read,
49        extension: Extension::None,
50    }
51}
52
53/// An abbreviated constructor for a "read" operand that is sign-extended to 64
54/// bits (quadword).
55///
56/// # Panics
57///
58/// This function panics if the location size is too large to extend.
59#[must_use]
60pub fn sxq(location: Location) -> Operand {
61    assert!(location.bits() <= 64);
62    Operand {
63        location,
64        mutability: Mutability::Read,
65        extension: Extension::SignExtendQuad,
66    }
67}
68
69/// An abbreviated constructor for a "read" operand that is sign-extended to 32
70/// bits (longword).
71///
72/// # Panics
73///
74/// This function panics if the location size is too large to extend.
75#[must_use]
76pub fn sxl(location: Location) -> Operand {
77    assert!(location.bits() <= 32);
78    Operand {
79        location,
80        mutability: Mutability::Read,
81        extension: Extension::SignExtendLong,
82    }
83}
84
85/// An abbreviated constructor for a "read" operand that is sign-extended to 16
86/// bits (word).
87///
88/// # Panics
89///
90/// This function panics if the location size is too large to extend.
91#[must_use]
92pub fn sxw(location: Location) -> Operand {
93    assert!(location.bits() <= 16);
94    Operand {
95        location,
96        mutability: Mutability::Read,
97        extension: Extension::SignExtendWord,
98    }
99}
100
101/// A format describes the operands for an instruction.
102pub struct Format {
103    /// This name, when combined with the instruction mnemonic, uniquely
104    /// identifies an instruction. The reference manual uses this name in the
105    /// "Instruction Operand Encoding" table.
106    pub name: String,
107    /// These operands should match the "Instruction" column ing the reference
108    /// manual.
109    pub operands: Vec<Operand>,
110}
111
112impl Format {
113    /// Iterate over the operand locations.
114    pub fn locations(&self) -> impl Iterator<Item = &Location> + '_ {
115        self.operands.iter().map(|o| &o.location)
116    }
117
118    /// Return the location of the operand that uses memory, if any; return
119    /// `None` otherwise.
120    pub fn uses_memory(&self) -> Option<Location> {
121        debug_assert!(self.locations().copied().filter(Location::uses_memory).count() <= 1);
122        self.locations().copied().find(Location::uses_memory)
123    }
124
125    /// Return `true` if any of the operands accepts a variable register (i.e.,
126    /// not a fixed register, immediate); return `false` otherwise.
127    #[must_use]
128    pub fn uses_variable_register(&self) -> bool {
129        self.locations().any(Location::uses_variable_register)
130    }
131
132    /// Collect into operand kinds.
133    pub fn operands_by_kind(&self) -> Vec<OperandKind> {
134        self.locations().map(Location::kind).collect()
135    }
136}
137
138impl core::fmt::Display for Format {
139    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
140        let Format { name, operands } = self;
141        let operands = operands
142            .iter()
143            .map(|operand| format!("{operand}"))
144            .collect::<Vec<_>>()
145            .join(", ");
146        write!(f, "{name}({operands})")
147    }
148}
149
150/// An x64 operand.
151///
152/// This is designed to look and feel like the operands as expressed in Intel's
153/// _Instruction Set Reference_.
154///
155/// ```
156/// # use cranelift_assembler_x64_meta::dsl::{Operand, r, rw, sxq, Location::*};
157/// assert_eq!(r(r8).to_string(), "r8");
158/// assert_eq!(rw(rm16).to_string(), "rm16[rw]");
159/// assert_eq!(sxq(imm32).to_string(), "imm32[sxq]");
160/// ```
161#[derive(Clone, Copy, Debug)]
162pub struct Operand {
163    /// The location of the data: memory, register, immediate.
164    pub location: Location,
165    /// An operand can be read-only or read-write.
166    pub mutability: Mutability,
167    /// Some operands are sign- or zero-extended.
168    pub extension: Extension,
169}
170
171impl core::fmt::Display for Operand {
172    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
173        let Self { location, mutability, extension } = self;
174        write!(f, "{location}")?;
175        let has_default_mutability = matches!(mutability, Mutability::Read);
176        let has_default_extension = matches!(extension, Extension::None);
177        match (has_default_mutability, has_default_extension) {
178            (true, true) => {}
179            (true, false) => write!(f, "[{extension}]")?,
180            (false, true) => write!(f, "[{mutability}]")?,
181            (false, false) => write!(f, "[{mutability},{extension}]")?,
182        }
183        Ok(())
184    }
185}
186
187impl From<Location> for Operand {
188    fn from(location: Location) -> Self {
189        let mutability = Mutability::default();
190        let extension = Extension::default();
191        Self { location, mutability, extension }
192    }
193}
194
195/// An operand location, as expressed in Intel's _Instruction Set Reference_.
196#[derive(Clone, Copy, Debug)]
197#[allow(non_camel_case_types, reason = "makes DSL definitions easier to read")]
198pub enum Location {
199    al,
200    ax,
201    eax,
202    rax,
203
204    cl,
205
206    imm8,
207    imm16,
208    imm32,
209
210    r8,
211    r16,
212    r32,
213    r64,
214
215    xmm,
216
217    rm8,
218    rm16,
219    rm32,
220    rm64,
221    rm128,
222}
223
224impl Location {
225    /// Return the number of bits accessed.
226    #[must_use]
227    pub fn bits(&self) -> u8 {
228        use Location::*;
229        match self {
230            al | cl | imm8 | r8 | rm8 => 8,
231            ax | imm16 | r16 | rm16 => 16,
232            eax | imm32 | r32 | rm32 => 32,
233            rax | r64 | rm64 => 64,
234            xmm | rm128 => 128,
235        }
236    }
237
238    /// Return the number of bytes accessed, for convenience.
239    #[must_use]
240    pub fn bytes(&self) -> u8 {
241        self.bits() / 8
242    }
243
244    /// Return `true` if the location accesses memory; `false` otherwise.
245    #[must_use]
246    pub fn uses_memory(&self) -> bool {
247        use Location::*;
248        match self {
249            al | cl | ax | eax | rax | imm8 | imm16 | imm32 | r8 | r16 | r32 | r64 | xmm => false,
250            rm8 | rm16 | rm32 | rm64 | rm128 => true,
251        }
252    }
253
254    /// Return `true` if the location accepts a variable register (i.e., not a
255    /// fixed register, immediate); return `false` otherwise.
256    #[must_use]
257    pub fn uses_variable_register(&self) -> bool {
258        use Location::*;
259        match self {
260            al | ax | eax | rax | cl | imm8 | imm16 | imm32 => false,
261            r8 | r16 | r32 | r64 | xmm | rm8 | rm16 | rm32 | rm64 | rm128 => true,
262        }
263    }
264
265    /// Convert the location to an [`OperandKind`].
266    #[must_use]
267    pub fn kind(&self) -> OperandKind {
268        use Location::*;
269        match self {
270            al | ax | eax | rax | cl => OperandKind::FixedReg(*self),
271            imm8 | imm16 | imm32 => OperandKind::Imm(*self),
272            r8 | r16 | r32 | r64 | xmm => OperandKind::Reg(*self),
273            rm8 | rm16 | rm32 | rm64 | rm128 => OperandKind::RegMem(*self),
274        }
275    }
276}
277
278impl core::fmt::Display for Location {
279    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
280        use Location::*;
281        match self {
282            al => write!(f, "al"),
283            ax => write!(f, "ax"),
284            eax => write!(f, "eax"),
285            rax => write!(f, "rax"),
286
287            cl => write!(f, "cl"),
288
289            imm8 => write!(f, "imm8"),
290            imm16 => write!(f, "imm16"),
291            imm32 => write!(f, "imm32"),
292
293            r8 => write!(f, "r8"),
294            r16 => write!(f, "r16"),
295            r32 => write!(f, "r32"),
296            r64 => write!(f, "r64"),
297
298            xmm => write!(f, "xmm"),
299
300            rm8 => write!(f, "rm8"),
301            rm16 => write!(f, "rm16"),
302            rm32 => write!(f, "rm32"),
303            rm64 => write!(f, "rm64"),
304            rm128 => write!(f, "rm128"),
305        }
306    }
307}
308
309/// Organize the operand locations by kind.
310///
311/// ```
312/// # use cranelift_assembler_x64_meta::dsl::{OperandKind, Location};
313/// let k: OperandKind = Location::imm32.kind();
314/// ```
315#[derive(Clone, Copy, Debug)]
316pub enum OperandKind {
317    FixedReg(Location),
318    Imm(Location),
319    Reg(Location),
320    RegMem(Location),
321}
322
323/// x64 operands can be mutable or not.
324///
325/// ```
326/// # use cranelift_assembler_x64_meta::dsl::{r, rw, Location::r8, Mutability};
327/// assert_eq!(r(r8).mutability, Mutability::Read);
328/// assert_eq!(rw(r8).mutability, Mutability::ReadWrite);
329/// ```
330#[derive(Clone, Copy, Debug, PartialEq)]
331pub enum Mutability {
332    Read,
333    ReadWrite,
334}
335
336impl Mutability {
337    /// Returns whether this represents a read of the operand in question.
338    ///
339    /// Note that for read/write operands this returns `true`.
340    pub fn is_read(&self) -> bool {
341        match self {
342            Mutability::Read | Mutability::ReadWrite => true,
343        }
344    }
345
346    /// Returns whether this represents a write of the operand in question.
347    ///
348    /// Note that for read/write operands this returns `true`.
349    pub fn is_write(&self) -> bool {
350        match self {
351            Mutability::Read => false,
352            Mutability::ReadWrite => true,
353        }
354    }
355}
356
357impl Default for Mutability {
358    fn default() -> Self {
359        Self::Read
360    }
361}
362
363impl core::fmt::Display for Mutability {
364    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
365        match self {
366            Self::Read => write!(f, "r"),
367            Self::ReadWrite => write!(f, "rw"),
368        }
369    }
370}
371
372/// x64 operands may be sign- or zero-extended.
373///
374/// ```
375/// # use cranelift_assembler_x64_meta::dsl::{Location::r8, sxw, Extension};
376/// assert_eq!(sxw(r8).extension, Extension::SignExtendWord);
377/// ```
378#[derive(Clone, Copy, Debug, PartialEq)]
379pub enum Extension {
380    None,
381    SignExtendQuad,
382    SignExtendLong,
383    SignExtendWord,
384}
385
386impl Extension {
387    /// Check if the extension is sign-extended.
388    #[must_use]
389    pub fn is_sign_extended(&self) -> bool {
390        matches!(self, Self::SignExtendQuad | Self::SignExtendLong | Self::SignExtendWord)
391    }
392}
393
394impl Default for Extension {
395    fn default() -> Self {
396        Self::None
397    }
398}
399
400impl core::fmt::Display for Extension {
401    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402        match self {
403            Extension::None => write!(f, ""),
404            Extension::SignExtendQuad => write!(f, "sxq"),
405            Extension::SignExtendLong => write!(f, "sxl"),
406            Extension::SignExtendWord => write!(f, "sxw"),
407        }
408    }
409}