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 = Operand>) -> Format {
21    Format {
22        name: name.into(),
23        operands: operands.into_iter().collect(),
24        eflags: Eflags::default(),
25    }
26}
27
28/// An abbreviated constructor for a "read-write" operand.
29///
30/// # Panics
31///
32/// This function panics if the location is an immediate (i.e., an immediate
33/// cannot be written to).
34#[must_use]
35pub fn rw(op: impl Into<Operand>) -> Operand {
36    let op = op.into();
37    assert!(!matches!(op.location.kind(), OperandKind::Imm(_)));
38    Operand {
39        mutability: Mutability::ReadWrite,
40        ..op
41    }
42}
43
44/// An abbreviated constructor for a "read" operand.
45#[must_use]
46pub fn r(op: impl Into<Operand>) -> Operand {
47    let op = op.into();
48    assert!(op.mutability.is_read());
49    op
50}
51
52/// An abbreviated constructor for a "write" operand.
53#[must_use]
54pub fn w(op: impl Into<Operand>) -> Operand {
55    let op = op.into();
56    Operand {
57        mutability: Mutability::Write,
58        ..op
59    }
60}
61
62/// An abbreviated constructor for a memory operand that requires alignment.
63pub fn align(location: Location) -> Operand {
64    assert!(location.uses_memory());
65    Operand {
66        align: true,
67        ..Operand::from(location)
68    }
69}
70
71/// An abbreviated constructor for an operand that is used by the instruction
72/// but not visible in its disassembly.
73pub fn implicit(location: Location) -> Operand {
74    assert!(matches!(location.kind(), OperandKind::FixedReg(_)));
75    Operand {
76        implicit: true,
77        ..Operand::from(location)
78    }
79}
80
81/// An abbreviated constructor for a "read" operand that is sign-extended to 64
82/// bits (quadword).
83///
84/// # Panics
85///
86/// This function panics if the location size is too large to extend.
87#[must_use]
88pub fn sxq(location: Location) -> Operand {
89    assert!(location.bits() <= 64);
90    Operand {
91        extension: Extension::SignExtendQuad,
92        ..Operand::from(location)
93    }
94}
95
96/// An abbreviated constructor for a "read" operand that is sign-extended to 32
97/// bits (longword).
98///
99/// # Panics
100///
101/// This function panics if the location size is too large to extend.
102#[must_use]
103pub fn sxl(location: Location) -> Operand {
104    assert!(location.bits() <= 32);
105    Operand {
106        extension: Extension::SignExtendLong,
107        ..Operand::from(location)
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        extension: Extension::SignExtendWord,
122        ..Operand::from(location)
123    }
124}
125
126/// A format describes the operands for an instruction.
127#[derive(Clone)]
128pub struct Format {
129    /// This name, when combined with the instruction mnemonic, uniquely
130    /// identifies an instruction. The reference manual uses this name in the
131    /// "Instruction Operand Encoding" table.
132    pub name: String,
133    /// These operands should match the "Instruction" column in the reference
134    /// manual.
135    pub operands: Vec<Operand>,
136    /// This should match eflags description of an instruction.
137    pub eflags: Eflags,
138}
139
140impl Format {
141    /// Iterate over the operand locations.
142    pub fn locations(&self) -> impl Iterator<Item = &Location> + '_ {
143        self.operands.iter().map(|o| &o.location)
144    }
145
146    /// Return the location of the operand that uses memory, if any; return
147    /// `None` otherwise.
148    pub fn uses_memory(&self) -> Option<Location> {
149        debug_assert!(
150            self.locations()
151                .copied()
152                .filter(Location::uses_memory)
153                .count()
154                <= 1
155        );
156        self.locations().copied().find(Location::uses_memory)
157    }
158
159    /// Return `true` if any of the operands accepts a register (i.e., not an
160    /// immediate); return `false` otherwise.
161    #[must_use]
162    pub fn uses_register(&self) -> bool {
163        self.locations().any(Location::uses_register)
164    }
165
166    /// Collect into operand kinds.
167    pub fn operands_by_kind(&self) -> Vec<OperandKind> {
168        self.locations().map(Location::kind).collect()
169    }
170
171    /// Set the EFLAGS mutability for this instruction.
172    pub fn flags(mut self, eflags: Eflags) -> Self {
173        self.eflags = eflags;
174        self
175    }
176
177    /// Return true if an instruction uses EFLAGS.
178    pub fn uses_eflags(&self) -> bool {
179        self.eflags != Eflags::None
180    }
181}
182
183impl core::fmt::Display for Format {
184    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
185        let Format {
186            name,
187            operands,
188            eflags,
189        } = self;
190        let operands = operands
191            .iter()
192            .map(|operand| format!("{operand}"))
193            .collect::<Vec<_>>()
194            .join(", ");
195        write!(f, "{name}({operands})")?;
196
197        if *eflags != Eflags::None {
198            write!(f, "[flags:{eflags}]")?;
199        }
200
201        Ok(())
202    }
203}
204
205/// An x64 operand.
206///
207/// This is designed to look and feel like the operands as expressed in Intel's
208/// _Instruction Set Reference_.
209///
210/// ```
211/// # use cranelift_assembler_x64_meta::dsl::{align, r, rw, sxq, Location::*};
212/// assert_eq!(r(r8).to_string(), "r8");
213/// assert_eq!(rw(rm16).to_string(), "rm16[rw]");
214/// assert_eq!(sxq(imm32).to_string(), "imm32[sxq]");
215/// assert_eq!(align(xmm_m128).to_string(), "xmm_m128[align]");
216/// ```
217#[derive(Clone, Copy, Debug)]
218pub struct Operand {
219    /// The location of the data: memory, register, immediate.
220    pub location: Location,
221    /// An operand can be read-only or read-write.
222    pub mutability: Mutability,
223    /// Some operands are sign- or zero-extended.
224    pub extension: Extension,
225    /// Some memory operands require alignment; `true` indicates that the memory
226    /// address used in the operand must align to the size of the operand (e.g.,
227    /// `m128` must be 16-byte aligned).
228    pub align: bool,
229    /// Some register operands are implicit: that is, they do not appear in the
230    /// disassembled output even though they are used in the instruction.
231    pub implicit: bool,
232}
233
234impl core::fmt::Display for Operand {
235    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
236        let Self {
237            location,
238            mutability,
239            extension,
240            align,
241            implicit,
242        } = self;
243        write!(f, "{location}")?;
244        let mut flags = vec![];
245        if !matches!(mutability, Mutability::Read) {
246            flags.push(format!("{mutability}"));
247        }
248        if !matches!(extension, Extension::None) {
249            flags.push(format!("{extension}"));
250        }
251        if *align != false {
252            flags.push("align".to_owned());
253        }
254        if *implicit {
255            flags.push("implicit".to_owned());
256        }
257        if !flags.is_empty() {
258            write!(f, "[{}]", flags.join(","))?;
259        }
260        Ok(())
261    }
262}
263
264impl From<Location> for Operand {
265    fn from(location: Location) -> Self {
266        let mutability = Mutability::default();
267        let extension = Extension::default();
268        let align = false;
269        let implicit = false;
270        Self {
271            location,
272            mutability,
273            extension,
274            align,
275            implicit,
276        }
277    }
278}
279
280/// The kind of register used in a [`Location`].
281pub enum RegClass {
282    Gpr,
283    Xmm,
284}
285
286impl core::fmt::Display for RegClass {
287    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
288        match self {
289            RegClass::Gpr => write!(f, "Gpr"),
290            RegClass::Xmm => write!(f, "Xmm"),
291        }
292    }
293}
294
295/// An operand location, as expressed in Intel's _Instruction Set Reference_.
296#[derive(Clone, Copy, Debug)]
297#[allow(non_camel_case_types, reason = "makes DSL definitions easier to read")]
298pub enum Location {
299    // Fixed registers.
300    al,
301    ax,
302    eax,
303    rax,
304    rbx,
305    dx,
306    edx,
307    rdx,
308    cl,
309    rcx,
310    xmm0,
311
312    // Immediate values.
313    imm8,
314    imm16,
315    imm32,
316    imm64,
317
318    // General-purpose registers, and their memory forms.
319    r8,
320    r16,
321    r32,
322    r32a,
323    r32b,
324    r64,
325    r64a,
326    r64b,
327    rm8,
328    rm16,
329    rm32,
330    rm64,
331
332    // XMM registers, and their memory forms.
333    xmm1,
334    xmm2,
335    xmm3,
336    xmm_m8,
337    xmm_m16,
338    xmm_m32,
339    xmm_m64,
340    xmm_m128,
341
342    // Memory-only locations.
343    m8,
344    m16,
345    m32,
346    m64,
347    m128,
348}
349
350impl Location {
351    /// Return the number of bits accessed.
352    #[must_use]
353    pub fn bits(&self) -> u16 {
354        use Location::*;
355        match self {
356            al | cl | imm8 | r8 | rm8 | m8 | xmm_m8 => 8,
357            ax | dx | imm16 | r16 | rm16 | m16 | xmm_m16 => 16,
358            eax | edx | imm32 | r32 | r32a | r32b | rm32 | m32 | xmm_m32 => 32,
359            rax | rbx | rcx | rdx | imm64 | r64 | r64a | r64b | rm64 | m64 | xmm_m64 => 64,
360            xmm1 | xmm2 | xmm3 | xmm_m128 | xmm0 | m128 => 128,
361        }
362    }
363
364    /// Return the number of bytes accessed, for convenience.
365    #[must_use]
366    pub fn bytes(&self) -> u16 {
367        self.bits() / 8
368    }
369
370    /// Return `true` if the location accesses memory; `false` otherwise.
371    #[must_use]
372    pub fn uses_memory(&self) -> bool {
373        use OperandKind::*;
374        match self.kind() {
375            FixedReg(_) | Imm(_) | Reg(_) => false,
376            RegMem(_) | Mem(_) => true,
377        }
378    }
379
380    /// Return `true` if any of the operands accepts a register (i.e., not an
381    /// immediate); return `false` otherwise.
382    #[must_use]
383    pub fn uses_register(&self) -> bool {
384        use OperandKind::*;
385        match self.kind() {
386            Imm(_) => false,
387            FixedReg(_) | Reg(_) | RegMem(_) | Mem(_) => true,
388        }
389    }
390
391    /// Convert the location to an [`OperandKind`].
392    #[must_use]
393    pub fn kind(&self) -> OperandKind {
394        use Location::*;
395        match self {
396            al | ax | eax | rax | rbx | cl | rcx | dx | edx | rdx | xmm0 => {
397                OperandKind::FixedReg(*self)
398            }
399            imm8 | imm16 | imm32 | imm64 => OperandKind::Imm(*self),
400            r8 | r16 | r32 | r32a | r32b | r64 | r64a | r64b | xmm1 | xmm2 | xmm3 => {
401                OperandKind::Reg(*self)
402            }
403            rm8 | rm16 | rm32 | rm64 | xmm_m8 | xmm_m16 | xmm_m32 | xmm_m64 | xmm_m128 => {
404                OperandKind::RegMem(*self)
405            }
406            m8 | m16 | m32 | m64 | m128 => OperandKind::Mem(*self),
407        }
408    }
409
410    /// If a location directly uses data from a register, return the register
411    /// class; otherwise, return `None`. Memory-only locations, though their
412    /// address is stored in a register, use data from memory and thus also
413    /// return `None`.
414    #[must_use]
415    pub fn reg_class(&self) -> Option<RegClass> {
416        use Location::*;
417        match self {
418            imm8 | imm16 | imm32 | imm64 | m8 | m16 | m32 | m64 | m128 => None,
419            al | ax | eax | rax | rbx | cl | rcx | dx | edx | rdx | r8 | r16 | r32 | r32a
420            | r32b | r64 | r64a | r64b | rm8 | rm16 | rm32 | rm64 => Some(RegClass::Gpr),
421            xmm1 | xmm2 | xmm3 | xmm_m8 | xmm_m16 | xmm_m32 | xmm_m64 | xmm_m128 | xmm0 => {
422                Some(RegClass::Xmm)
423            }
424        }
425    }
426}
427
428impl core::fmt::Display for Location {
429    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
430        use Location::*;
431        match self {
432            imm8 => write!(f, "imm8"),
433            imm16 => write!(f, "imm16"),
434            imm32 => write!(f, "imm32"),
435            imm64 => write!(f, "imm64"),
436
437            al => write!(f, "al"),
438            ax => write!(f, "ax"),
439            eax => write!(f, "eax"),
440            rax => write!(f, "rax"),
441            rbx => write!(f, "rbx"),
442            cl => write!(f, "cl"),
443            rcx => write!(f, "rcx"),
444            dx => write!(f, "dx"),
445            edx => write!(f, "edx"),
446            rdx => write!(f, "rdx"),
447            xmm0 => write!(f, "xmm0"),
448
449            r8 => write!(f, "r8"),
450            r16 => write!(f, "r16"),
451            r32 => write!(f, "r32"),
452            r32a => write!(f, "r32a"),
453            r32b => write!(f, "r32b"),
454            r64 => write!(f, "r64"),
455            r64a => write!(f, "r64a"),
456            r64b => write!(f, "r64b"),
457            rm8 => write!(f, "rm8"),
458            rm16 => write!(f, "rm16"),
459            rm32 => write!(f, "rm32"),
460            rm64 => write!(f, "rm64"),
461
462            xmm1 => write!(f, "xmm1"),
463            xmm2 => write!(f, "xmm2"),
464            xmm3 => write!(f, "xmm3"),
465            xmm_m8 => write!(f, "xmm_m8"),
466            xmm_m16 => write!(f, "xmm_m16"),
467            xmm_m32 => write!(f, "xmm_m32"),
468            xmm_m64 => write!(f, "xmm_m64"),
469            xmm_m128 => write!(f, "xmm_m128"),
470
471            m8 => write!(f, "m8"),
472            m16 => write!(f, "m16"),
473            m32 => write!(f, "m32"),
474            m64 => write!(f, "m64"),
475            m128 => write!(f, "m128"),
476        }
477    }
478}
479
480/// Organize the operand locations by kind.
481///
482/// ```
483/// # use cranelift_assembler_x64_meta::dsl::{OperandKind, Location};
484/// let k: OperandKind = Location::imm32.kind();
485/// ```
486#[derive(Clone, Copy, Debug)]
487pub enum OperandKind {
488    FixedReg(Location),
489    Imm(Location),
490    Reg(Location),
491    RegMem(Location),
492    Mem(Location),
493}
494
495/// x64 operands can be mutable or not.
496///
497/// ```
498/// # use cranelift_assembler_x64_meta::dsl::{r, rw, Location::r8, Mutability};
499/// assert_eq!(r(r8).mutability, Mutability::Read);
500/// assert_eq!(rw(r8).mutability, Mutability::ReadWrite);
501/// ```
502#[derive(Clone, Copy, Debug, PartialEq)]
503pub enum Mutability {
504    Read,
505    ReadWrite,
506    Write,
507}
508
509impl Mutability {
510    /// Returns whether this represents a read of the operand in question.
511    ///
512    /// Note that for read/write operands this returns `true`.
513    pub fn is_read(&self) -> bool {
514        match self {
515            Mutability::Read | Mutability::ReadWrite => true,
516            Mutability::Write => false,
517        }
518    }
519
520    /// Returns whether this represents a write of the operand in question.
521    ///
522    /// Note that for read/write operands this returns `true`.
523    pub fn is_write(&self) -> bool {
524        match self {
525            Mutability::Read => false,
526            Mutability::ReadWrite | Mutability::Write => true,
527        }
528    }
529}
530
531impl Default for Mutability {
532    fn default() -> Self {
533        Self::Read
534    }
535}
536
537impl core::fmt::Display for Mutability {
538    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
539        match self {
540            Self::Read => write!(f, "r"),
541            Self::ReadWrite => write!(f, "rw"),
542            Self::Write => write!(f, "w"),
543        }
544    }
545}
546
547/// x64 operands may be sign- or zero-extended.
548///
549/// ```
550/// # use cranelift_assembler_x64_meta::dsl::{Location::r8, sxw, Extension};
551/// assert_eq!(sxw(r8).extension, Extension::SignExtendWord);
552/// ```
553#[derive(Clone, Copy, Debug, PartialEq)]
554pub enum Extension {
555    None,
556    SignExtendQuad,
557    SignExtendLong,
558    SignExtendWord,
559}
560
561impl Extension {
562    /// Check if the extension is sign-extended.
563    #[must_use]
564    pub fn is_sign_extended(&self) -> bool {
565        matches!(
566            self,
567            Self::SignExtendQuad | Self::SignExtendLong | Self::SignExtendWord
568        )
569    }
570}
571
572impl Default for Extension {
573    fn default() -> Self {
574        Self::None
575    }
576}
577
578impl core::fmt::Display for Extension {
579    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
580        match self {
581            Extension::None => write!(f, ""),
582            Extension::SignExtendQuad => write!(f, "sxq"),
583            Extension::SignExtendLong => write!(f, "sxl"),
584            Extension::SignExtendWord => write!(f, "sxw"),
585        }
586    }
587}
588
589/// Describes if an instruction uses EFLAGS, and whether it reads, writes, or
590/// reads/writes the EFLAGS register.
591/// In the future, we might want to model specific EFLAGS bits instead of the
592/// entire EFLAGS register.
593/// Some related discussion in this GitHub issue
594/// https://github.com/bytecodealliance/wasmtime/issues/10298
595#[derive(Clone, Copy, Debug, PartialEq)]
596pub enum Eflags {
597    None,
598    R,
599    W,
600    RW,
601}
602
603impl Eflags {
604    /// Returns whether this represents a read of any bit in the EFLAGS
605    /// register.
606    pub fn is_read(&self) -> bool {
607        match self {
608            Eflags::None | Eflags::W => false,
609            Eflags::R | Eflags::RW => true,
610        }
611    }
612
613    /// Returns whether this represents a writes to any bit in the EFLAGS
614    /// register.
615    pub fn is_write(&self) -> bool {
616        match self {
617            Eflags::None | Eflags::R => false,
618            Eflags::W | Eflags::RW => true,
619        }
620    }
621}
622
623impl Default for Eflags {
624    fn default() -> Self {
625        Self::None
626    }
627}
628
629impl core::fmt::Display for Eflags {
630    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631        match self {
632            Self::None => write!(f, ""),
633            Self::R => write!(f, "r"),
634            Self::W => write!(f, "w"),
635            Self::RW => write!(f, "rw"),
636        }
637    }
638}