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(
21    name: impl Into<String>,
22    operands: impl IntoIterator<Item = impl Into<Operand>>,
23) -> Format {
24    Format {
25        name: name.into(),
26        operands: operands.into_iter().map(Into::into).collect(),
27    }
28}
29
30/// An abbreviated constructor for a "read-write" operand.
31///
32/// # Panics
33///
34/// This function panics if the location is an immediate (i.e., an immediate
35/// cannot be written to).
36#[must_use]
37pub fn rw(location: Location) -> Operand {
38    assert!(!matches!(location.kind(), OperandKind::Imm(_)));
39    Operand {
40        location,
41        mutability: Mutability::ReadWrite,
42        extension: Extension::default(),
43        align: false,
44    }
45}
46
47/// An abbreviated constructor for a "read" operand.
48#[must_use]
49pub fn r(op: impl Into<Operand>) -> Operand {
50    let op = op.into();
51    assert!(op.mutability.is_read());
52    op
53}
54
55/// An abbreviated constructor for a "write" operand.
56#[must_use]
57pub fn w(location: Location) -> Operand {
58    Operand {
59        location,
60        mutability: Mutability::Write,
61        extension: Extension::None,
62        align: false,
63    }
64}
65
66/// An abbreviated constructor for a memory operand that requires alignment.
67pub fn align(location: Location) -> Operand {
68    assert!(location.uses_memory());
69    Operand {
70        location,
71        mutability: Mutability::Read,
72        extension: Extension::None,
73        align: true,
74    }
75}
76
77/// An abbreviated constructor for a "read" operand that is sign-extended to 64
78/// bits (quadword).
79///
80/// # Panics
81///
82/// This function panics if the location size is too large to extend.
83#[must_use]
84pub fn sxq(location: Location) -> Operand {
85    assert!(location.bits() <= 64);
86    Operand {
87        location,
88        mutability: Mutability::Read,
89        extension: Extension::SignExtendQuad,
90        align: false,
91    }
92}
93
94/// An abbreviated constructor for a "read" operand that is sign-extended to 32
95/// bits (longword).
96///
97/// # Panics
98///
99/// This function panics if the location size is too large to extend.
100#[must_use]
101pub fn sxl(location: Location) -> Operand {
102    assert!(location.bits() <= 32);
103    Operand {
104        location,
105        mutability: Mutability::Read,
106        extension: Extension::SignExtendLong,
107        align: false,
108    }
109}
110
111/// An abbreviated constructor for a "read" operand that is sign-extended to 16
112/// bits (word).
113///
114/// # Panics
115///
116/// This function panics if the location size is too large to extend.
117#[must_use]
118pub fn sxw(location: Location) -> Operand {
119    assert!(location.bits() <= 16);
120    Operand {
121        location,
122        mutability: Mutability::Read,
123        extension: Extension::SignExtendWord,
124        align: false,
125    }
126}
127
128/// A format describes the operands for an instruction.
129pub struct Format {
130    /// This name, when combined with the instruction mnemonic, uniquely
131    /// identifies an instruction. The reference manual uses this name in the
132    /// "Instruction Operand Encoding" table.
133    pub name: String,
134    /// These operands should match the "Instruction" column ing the reference
135    /// manual.
136    pub operands: Vec<Operand>,
137}
138
139impl Format {
140    /// Iterate over the operand locations.
141    pub fn locations(&self) -> impl Iterator<Item = &Location> + '_ {
142        self.operands.iter().map(|o| &o.location)
143    }
144
145    /// Return the location of the operand that uses memory, if any; return
146    /// `None` otherwise.
147    pub fn uses_memory(&self) -> Option<Location> {
148        debug_assert!(
149            self.locations()
150                .copied()
151                .filter(Location::uses_memory)
152                .count()
153                <= 1
154        );
155        self.locations().copied().find(Location::uses_memory)
156    }
157
158    /// Return `true` if any of the operands accepts a register (i.e., not an
159    /// immediate); return `false` otherwise.
160    #[must_use]
161    pub fn uses_register(&self) -> bool {
162        self.locations().any(Location::uses_register)
163    }
164
165    /// Collect into operand kinds.
166    pub fn operands_by_kind(&self) -> Vec<OperandKind> {
167        self.locations().map(Location::kind).collect()
168    }
169}
170
171impl core::fmt::Display for Format {
172    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
173        let Format { name, operands } = self;
174        let operands = operands
175            .iter()
176            .map(|operand| format!("{operand}"))
177            .collect::<Vec<_>>()
178            .join(", ");
179        write!(f, "{name}({operands})")
180    }
181}
182
183/// An x64 operand.
184///
185/// This is designed to look and feel like the operands as expressed in Intel's
186/// _Instruction Set Reference_.
187///
188/// ```
189/// # use cranelift_assembler_x64_meta::dsl::{align, r, rw, sxq, Location::*};
190/// assert_eq!(r(r8).to_string(), "r8");
191/// assert_eq!(rw(rm16).to_string(), "rm16[rw]");
192/// assert_eq!(sxq(imm32).to_string(), "imm32[sxq]");
193/// assert_eq!(align(xmm_m128).to_string(), "xmm_m128[align]");
194/// ```
195#[derive(Clone, Copy, Debug)]
196pub struct Operand {
197    /// The location of the data: memory, register, immediate.
198    pub location: Location,
199    /// An operand can be read-only or read-write.
200    pub mutability: Mutability,
201    /// Some operands are sign- or zero-extended.
202    pub extension: Extension,
203    /// Some memory operands require alignment; `true` indicates that the memory
204    /// address used in the operand must align to the size of the operand (e.g.,
205    /// `m128` must be 16-byte aligned).
206    pub align: bool,
207}
208
209impl core::fmt::Display for Operand {
210    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
211        let Self {
212            location,
213            mutability,
214            extension,
215            align,
216        } = self;
217        write!(f, "{location}")?;
218        let mut flags = vec![];
219        if !matches!(mutability, Mutability::Read) {
220            flags.push(format!("{mutability}"));
221        }
222        if !matches!(extension, Extension::None) {
223            flags.push(format!("{extension}"));
224        }
225        if *align != false {
226            flags.push("align".to_owned());
227        }
228        if !flags.is_empty() {
229            write!(f, "[{}]", flags.join(","))?;
230        }
231        Ok(())
232    }
233}
234
235impl From<Location> for Operand {
236    fn from(location: Location) -> Self {
237        let mutability = Mutability::default();
238        let extension = Extension::default();
239        let align = false;
240        Self {
241            location,
242            mutability,
243            extension,
244            align,
245        }
246    }
247}
248
249/// The kind of register used in a [`Location`].
250pub enum RegClass {
251    Gpr,
252    Xmm,
253}
254
255impl core::fmt::Display for RegClass {
256    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
257        match self {
258            RegClass::Gpr => write!(f, "Gpr"),
259            RegClass::Xmm => write!(f, "Xmm"),
260        }
261    }
262}
263
264/// An operand location, as expressed in Intel's _Instruction Set Reference_.
265#[derive(Clone, Copy, Debug)]
266#[allow(non_camel_case_types, reason = "makes DSL definitions easier to read")]
267pub enum Location {
268    // Fixed registers.
269    al,
270    ax,
271    eax,
272    rax,
273    cl,
274
275    // Immediate values.
276    imm8,
277    imm16,
278    imm32,
279
280    // General-purpose registers, and their memory forms.
281    r8,
282    r16,
283    r32,
284    r64,
285    rm8,
286    rm16,
287    rm32,
288    rm64,
289
290    // XMM registers, and their memory forms.
291    xmm,
292    xmm_m32,
293    xmm_m64,
294    xmm_m128,
295
296    // Memory-only locations.
297    m8,
298    m16,
299    m32,
300    m64,
301}
302
303impl Location {
304    /// Return the number of bits accessed.
305    #[must_use]
306    pub fn bits(&self) -> u8 {
307        use Location::*;
308        match self {
309            al | cl | imm8 | r8 | rm8 | m8 => 8,
310            ax | imm16 | r16 | rm16 | m16 => 16,
311            eax | imm32 | r32 | rm32 | m32 | xmm_m32 => 32,
312            rax | r64 | rm64 | m64 | xmm_m64 => 64,
313            xmm | xmm_m128 => 128,
314        }
315    }
316
317    /// Return the number of bytes accessed, for convenience.
318    #[must_use]
319    pub fn bytes(&self) -> u8 {
320        self.bits() / 8
321    }
322
323    /// Return `true` if the location accesses memory; `false` otherwise.
324    #[must_use]
325    pub fn uses_memory(&self) -> bool {
326        use Location::*;
327        match self {
328            al | cl | ax | eax | rax | imm8 | imm16 | imm32 | r8 | r16 | r32 | r64 | xmm => false,
329            rm8 | rm16 | rm32 | rm64 | xmm_m32 | xmm_m64 | xmm_m128 | m8 | m16 | m32 | m64 => true,
330        }
331    }
332
333    /// Return `true` if any of the operands accepts a register (i.e., not an
334    /// immediate); return `false` otherwise.
335    #[must_use]
336    pub fn uses_register(&self) -> bool {
337        use Location::*;
338        match self {
339            imm8 | imm16 | imm32 => false,
340            al | ax | eax | rax | cl | r8 | r16 | r32 | r64 | rm8 | rm16 | rm32 | rm64 | xmm
341            | xmm_m32 | xmm_m64 | xmm_m128 | m8 | m16 | m32 | m64 => true,
342        }
343    }
344
345    /// Convert the location to an [`OperandKind`].
346    #[must_use]
347    pub fn kind(&self) -> OperandKind {
348        use Location::*;
349        match self {
350            al | ax | eax | rax | cl => OperandKind::FixedReg(*self),
351            imm8 | imm16 | imm32 => OperandKind::Imm(*self),
352            r8 | r16 | r32 | r64 | xmm => OperandKind::Reg(*self),
353            rm8 | rm16 | rm32 | rm64 | xmm_m32 | xmm_m64 | xmm_m128 => OperandKind::RegMem(*self),
354            m8 | m16 | m32 | m64 => OperandKind::Mem(*self),
355        }
356    }
357
358    /// If a location directly uses data from a register, return the register
359    /// class; otherwise, return `None`. Memory-only locations, though their
360    /// address is stored in a register, use data from memory and thus also
361    /// return `None`.
362    #[must_use]
363    pub fn reg_class(&self) -> Option<RegClass> {
364        use Location::*;
365        match self {
366            imm8 | imm16 | imm32 | m8 | m16 | m32 | m64 => None,
367            al | ax | eax | rax | cl | r8 | r16 | r32 | r64 | rm8 | rm16 | rm32 | rm64 => {
368                Some(RegClass::Gpr)
369            }
370            xmm | xmm_m32 | xmm_m64 | xmm_m128 => Some(RegClass::Xmm),
371        }
372    }
373}
374
375impl core::fmt::Display for Location {
376    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
377        use Location::*;
378        match self {
379            imm8 => write!(f, "imm8"),
380            imm16 => write!(f, "imm16"),
381            imm32 => write!(f, "imm32"),
382
383            al => write!(f, "al"),
384            ax => write!(f, "ax"),
385            eax => write!(f, "eax"),
386            rax => write!(f, "rax"),
387            cl => write!(f, "cl"),
388
389            r8 => write!(f, "r8"),
390            r16 => write!(f, "r16"),
391            r32 => write!(f, "r32"),
392            r64 => write!(f, "r64"),
393            rm8 => write!(f, "rm8"),
394            rm16 => write!(f, "rm16"),
395            rm32 => write!(f, "rm32"),
396            rm64 => write!(f, "rm64"),
397
398            xmm => write!(f, "xmm"),
399            xmm_m32 => write!(f, "xmm_m32"),
400            xmm_m64 => write!(f, "xmm_m64"),
401            xmm_m128 => write!(f, "xmm_m128"),
402
403            m8 => write!(f, "m8"),
404            m16 => write!(f, "m16"),
405            m32 => write!(f, "m32"),
406            m64 => write!(f, "m64"),
407        }
408    }
409}
410
411/// Organize the operand locations by kind.
412///
413/// ```
414/// # use cranelift_assembler_x64_meta::dsl::{OperandKind, Location};
415/// let k: OperandKind = Location::imm32.kind();
416/// ```
417#[derive(Clone, Copy, Debug)]
418pub enum OperandKind {
419    FixedReg(Location),
420    Imm(Location),
421    Reg(Location),
422    RegMem(Location),
423    Mem(Location),
424}
425
426/// x64 operands can be mutable or not.
427///
428/// ```
429/// # use cranelift_assembler_x64_meta::dsl::{r, rw, Location::r8, Mutability};
430/// assert_eq!(r(r8).mutability, Mutability::Read);
431/// assert_eq!(rw(r8).mutability, Mutability::ReadWrite);
432/// ```
433#[derive(Clone, Copy, Debug, PartialEq)]
434pub enum Mutability {
435    Read,
436    ReadWrite,
437    Write,
438}
439
440impl Mutability {
441    /// Returns whether this represents a read of the operand in question.
442    ///
443    /// Note that for read/write operands this returns `true`.
444    pub fn is_read(&self) -> bool {
445        match self {
446            Mutability::Read | Mutability::ReadWrite => true,
447            Mutability::Write => false,
448        }
449    }
450
451    /// Returns whether this represents a write of the operand in question.
452    ///
453    /// Note that for read/write operands this returns `true`.
454    pub fn is_write(&self) -> bool {
455        match self {
456            Mutability::Read => false,
457            Mutability::ReadWrite | Mutability::Write => true,
458        }
459    }
460}
461
462impl Default for Mutability {
463    fn default() -> Self {
464        Self::Read
465    }
466}
467
468impl core::fmt::Display for Mutability {
469    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
470        match self {
471            Self::Read => write!(f, "r"),
472            Self::ReadWrite => write!(f, "rw"),
473            Self::Write => write!(f, "w"),
474        }
475    }
476}
477
478/// x64 operands may be sign- or zero-extended.
479///
480/// ```
481/// # use cranelift_assembler_x64_meta::dsl::{Location::r8, sxw, Extension};
482/// assert_eq!(sxw(r8).extension, Extension::SignExtendWord);
483/// ```
484#[derive(Clone, Copy, Debug, PartialEq)]
485pub enum Extension {
486    None,
487    SignExtendQuad,
488    SignExtendLong,
489    SignExtendWord,
490}
491
492impl Extension {
493    /// Check if the extension is sign-extended.
494    #[must_use]
495    pub fn is_sign_extended(&self) -> bool {
496        matches!(
497            self,
498            Self::SignExtendQuad | Self::SignExtendLong | Self::SignExtendWord
499        )
500    }
501}
502
503impl Default for Extension {
504    fn default() -> Self {
505        Self::None
506    }
507}
508
509impl core::fmt::Display for Extension {
510    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
511        match self {
512            Extension::None => write!(f, ""),
513            Extension::SignExtendQuad => write!(f, "sxq"),
514            Extension::SignExtendLong => write!(f, "sxl"),
515            Extension::SignExtendWord => write!(f, "sxw"),
516        }
517    }
518}