Skip to main content

cranelift_assembler_x64/
mem.rs

1//! Memory operands to instructions.
2
3use alloc::string::{String, ToString};
4
5use crate::api::{AsReg, CodeSink, Constant, KnownOffset, Label, TrapCode};
6use crate::gpr::{self, NonRspGpr, Size};
7use crate::rex::{Disp, RexPrefix, encode_modrm, encode_sib};
8
9/// x64 memory addressing modes.
10#[derive(Copy, Clone, Debug, PartialEq)]
11#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
12pub enum Amode<R: AsReg> {
13    ImmReg {
14        base: R,
15        simm32: AmodeOffsetPlusKnownOffset,
16        trap: Option<TrapCode>,
17    },
18    ImmRegRegShift {
19        base: R,
20        index: NonRspGpr<R>,
21        scale: Scale,
22        simm32: AmodeOffset,
23        trap: Option<TrapCode>,
24    },
25    RipRelative {
26        target: DeferredTarget,
27    },
28}
29
30impl<R: AsReg> Amode<R> {
31    /// Return the [`TrapCode`] associated with this [`Amode`], if any.
32    pub fn trap_code(&self) -> Option<TrapCode> {
33        match self {
34            Amode::ImmReg { trap, .. } | Amode::ImmRegRegShift { trap, .. } => *trap,
35            Amode::RipRelative { .. } => None,
36        }
37    }
38
39    /// Return the [`RexPrefix`] for each variant of this [`Amode`].
40    #[must_use]
41    pub(crate) fn as_rex_prefix(&self, enc_reg: u8, has_w_bit: bool, uses_8bit: bool) -> RexPrefix {
42        match self {
43            Amode::ImmReg { base, .. } => {
44                RexPrefix::mem_op(enc_reg, base.enc(), has_w_bit, uses_8bit)
45            }
46            Amode::ImmRegRegShift { base, index, .. } => {
47                RexPrefix::three_op(enc_reg, index.enc(), base.enc(), has_w_bit, uses_8bit)
48            }
49            Amode::RipRelative { .. } => RexPrefix::two_op(enc_reg, 0, has_w_bit, uses_8bit),
50        }
51    }
52
53    /// Emit the ModR/M, SIB, and displacement suffixes as needed for this
54    /// `Amode`.
55    pub(crate) fn encode_rex_suffixes(
56        &self,
57        sink: &mut impl CodeSink,
58        enc_reg: u8,
59        bytes_at_end: u8,
60        evex_scaling: Option<i8>,
61    ) {
62        emit_modrm_sib_disp(sink, enc_reg, self, bytes_at_end, evex_scaling);
63    }
64
65    /// Return the registers for encoding the `b` and `x` bits (e.g., in a VEX
66    /// prefix).
67    ///
68    /// During encoding, the `b` bit is set by the topmost bit (the fourth bit)
69    /// of either the `reg` register or, if this is a memory address, the `base`
70    /// register. The `x` bit is set by the `index` register, when used.
71    pub(crate) fn encode_bx_regs(&self) -> (Option<u8>, Option<u8>) {
72        match self {
73            Amode::ImmReg { base, .. } => (Some(base.enc()), None),
74            Amode::ImmRegRegShift { base, index, .. } => (Some(base.enc()), Some(index.enc())),
75            Amode::RipRelative { .. } => (None, None),
76        }
77    }
78}
79
80/// A 32-bit immediate for address offsets.
81#[derive(Clone, Copy, Debug, PartialEq)]
82pub struct AmodeOffset(i32);
83
84impl AmodeOffset {
85    pub const ZERO: AmodeOffset = AmodeOffset::new(0);
86
87    #[must_use]
88    pub const fn new(value: i32) -> Self {
89        Self(value)
90    }
91
92    #[must_use]
93    pub fn value(self) -> i32 {
94        self.0
95    }
96}
97
98impl From<i32> for AmodeOffset {
99    fn from(value: i32) -> Self {
100        Self(value)
101    }
102}
103
104impl core::fmt::LowerHex for AmodeOffset {
105    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
106        // This rather complex implementation is necessary to match how
107        // `capstone` pretty-prints memory immediates; XED (the alternate form)
108        // always uses hexadecimal.
109        if self.0 == 0 {
110            return Ok(());
111        }
112        if self.0 < 0 {
113            write!(f, "-")?;
114        }
115        if f.alternate() || self.0 > 9 || self.0 < -9 {
116            write!(f, "0x")?;
117        }
118        let abs = match self.0.checked_abs() {
119            Some(i) => i,
120            None => -2_147_483_648,
121        };
122        // Not `LowerHex::fmt(&abs, f)`: `f` may carry the alternate flag, which
123        // would make the integer emit a second `0x`.
124        write!(f, "{abs:x}")
125    }
126}
127
128/// An [`AmodeOffset`] immediate with an optional known offset.
129///
130/// Cranelift does not know certain offsets until emission time. To accommodate
131/// Cranelift, this structure stores an optional [`KnownOffset`]. The following
132/// happens immediately before emission:
133/// - the [`KnownOffset`] is looked up, mapping it to an offset value
134/// - the [`AmodeOffset`] value is added to the offset value
135#[derive(Copy, Clone, Debug, PartialEq)]
136pub struct AmodeOffsetPlusKnownOffset {
137    pub simm32: AmodeOffset,
138    pub offset: Option<KnownOffset>,
139}
140
141impl AmodeOffsetPlusKnownOffset {
142    pub const ZERO: AmodeOffsetPlusKnownOffset = AmodeOffsetPlusKnownOffset {
143        simm32: AmodeOffset::ZERO,
144        offset: None,
145    };
146
147    /// # Panics
148    ///
149    /// Panics if the sum of the immediate and the known offset value overflows.
150    #[must_use]
151    pub fn value(&self, sink: &impl CodeSink) -> i32 {
152        let known_offset = match self.offset {
153            Some(offset) => sink.known_offset(offset),
154            None => 0,
155        };
156        known_offset
157            .checked_add(self.simm32.value())
158            .expect("no wrapping")
159    }
160}
161
162impl core::fmt::LowerHex for AmodeOffsetPlusKnownOffset {
163    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
164        if let Some(offset) = self.offset {
165            write!(f, "<offset:{offset}>+")?;
166        }
167        core::fmt::LowerHex::fmt(&self.simm32, f)
168    }
169}
170
171/// For RIP-relative addressing, keep track of the [`CodeSink`]-specific target.
172#[derive(Copy, Clone, Debug, PartialEq)]
173#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
174pub enum DeferredTarget {
175    Label(Label),
176    Constant(Constant),
177    None,
178}
179
180impl<R: AsReg> core::fmt::Display for Amode<R> {
181    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
182        let pointer_width = Size::Quadword;
183        // XED prints no space after the commas and always states the scale.
184        let xed = f.alternate();
185        match self {
186            Amode::ImmReg { simm32, base, .. } => {
187                // Note: size is always 8; the address is 64 bits,
188                // even if the addressed operand is smaller.
189                let base = base.to_string(Some(pointer_width));
190                if xed {
191                    write!(f, "{simm32:#x}({base})")
192                } else {
193                    write!(f, "{simm32:x}({base})")
194                }
195            }
196            Amode::ImmRegRegShift {
197                simm32,
198                base,
199                index,
200                scale,
201                ..
202            } => {
203                let base = base.to_string(Some(pointer_width));
204                let index = index.to_string(pointer_width);
205                let shift = scale.shift();
206                if xed {
207                    write!(f, "{simm32:#x}({base},{index},{shift})")
208                } else if shift > 1 {
209                    write!(f, "{simm32:x}({base}, {index}, {shift})")
210                } else {
211                    write!(f, "{simm32:x}({base}, {index})")
212                }
213            }
214            Amode::RipRelative { .. } => write!(f, "(%rip)"),
215        }
216    }
217}
218
219/// The scaling factor for the index register in certain [`Amode`]s.
220#[derive(Copy, Clone, Debug, PartialEq)]
221#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
222pub enum Scale {
223    One,
224    Two,
225    Four,
226    Eight,
227}
228
229impl Scale {
230    /// Create a new [`Scale`] from its hardware encoding.
231    ///
232    /// # Panics
233    ///
234    /// Panics if `enc` is not a valid encoding for a scale (0-3).
235    #[must_use]
236    pub fn new(enc: u8) -> Self {
237        match enc {
238            0b00 => Scale::One,
239            0b01 => Scale::Two,
240            0b10 => Scale::Four,
241            0b11 => Scale::Eight,
242            _ => panic!("invalid scale encoding: {enc}"),
243        }
244    }
245
246    /// Return the hardware encoding of this [`Scale`].
247    fn enc(&self) -> u8 {
248        match self {
249            Scale::One => 0b00,
250            Scale::Two => 0b01,
251            Scale::Four => 0b10,
252            Scale::Eight => 0b11,
253        }
254    }
255
256    /// Return how much this [`Scale`] will shift the value in the index
257    /// register of the SIB byte.
258    ///
259    /// This is useful for pretty-printing; when encoding, one usually needs
260    /// [`Scale::enc`].
261    fn shift(&self) -> u8 {
262        1 << self.enc()
263    }
264}
265
266/// A general-purpose register or memory operand.
267#[derive(Copy, Clone, Debug, PartialEq)]
268#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
269#[allow(
270    clippy::module_name_repetitions,
271    reason = "'GprMem' indicates this has GPR and memory variants"
272)]
273pub enum GprMem<R: AsReg, M: AsReg> {
274    Gpr(R),
275    Mem(Amode<M>),
276}
277
278impl<R: AsReg, M: AsReg> GprMem<R, M> {
279    /// Whether this operand is a memory reference.
280    #[must_use]
281    pub fn is_memory(&self) -> bool {
282        matches!(self, GprMem::Mem(_))
283    }
284
285    /// Pretty-print the operand.
286    pub fn to_string(&self, size: Size) -> String {
287        match self {
288            GprMem::Gpr(gpr) => gpr.to_string(Some(size)),
289            GprMem::Mem(amode) => amode.to_string(),
290        }
291    }
292
293    /// Pretty-print the operand in XED's dialect; see [`Amode`]'s `Display`.
294    pub fn to_string_xed(&self, size: Size) -> String {
295        match self {
296            GprMem::Gpr(gpr) => gpr.to_string(Some(size)),
297            GprMem::Mem(amode) => alloc::format!("{amode:#}"),
298        }
299    }
300
301    /// Return the [`RexPrefix`] for each variant of this [`GprMem`].
302    #[must_use]
303    pub(crate) fn as_rex_prefix(&self, enc_reg: u8, has_w_bit: bool, uses_8bit: bool) -> RexPrefix {
304        match self {
305            GprMem::Gpr(rm) => RexPrefix::two_op(enc_reg, rm.enc(), has_w_bit, uses_8bit),
306            GprMem::Mem(amode) => amode.as_rex_prefix(enc_reg, has_w_bit, uses_8bit),
307        }
308    }
309
310    /// Emit the ModR/M, SIB, and displacement suffixes for this [`GprMem`].
311    pub(crate) fn encode_rex_suffixes(
312        &self,
313        sink: &mut impl CodeSink,
314        enc_reg: u8,
315        bytes_at_end: u8,
316        evex_scaling: Option<i8>,
317    ) {
318        match self {
319            GprMem::Gpr(gpr) => {
320                sink.put1(encode_modrm(0b11, enc_reg & 0b111, gpr.enc() & 0b111));
321            }
322            GprMem::Mem(amode) => {
323                amode.encode_rex_suffixes(sink, enc_reg, bytes_at_end, evex_scaling);
324            }
325        }
326    }
327
328    /// Same as `XmmMem::encode_bx_regs`, but for `GprMem`.
329    pub(crate) fn encode_bx_regs(&self) -> (Option<u8>, Option<u8>) {
330        match self {
331            GprMem::Gpr(reg) => (Some(reg.enc()), None),
332            GprMem::Mem(amode) => amode.encode_bx_regs(),
333        }
334    }
335}
336
337impl<R: AsReg, M: AsReg> From<R> for GprMem<R, M> {
338    fn from(reg: R) -> GprMem<R, M> {
339        GprMem::Gpr(reg)
340    }
341}
342
343impl<R: AsReg, M: AsReg> From<Amode<M>> for GprMem<R, M> {
344    fn from(amode: Amode<M>) -> GprMem<R, M> {
345        GprMem::Mem(amode)
346    }
347}
348
349/// An XMM register or memory operand.
350#[derive(Copy, Clone, Debug)]
351#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
352#[allow(
353    clippy::module_name_repetitions,
354    reason = "'XmmMem' indicates this has Xmm and memory variants"
355)]
356pub enum XmmMem<R: AsReg, M: AsReg> {
357    Xmm(R),
358    Mem(Amode<M>),
359}
360
361impl<R: AsReg, M: AsReg> XmmMem<R, M> {
362    /// Whether this operand is a memory reference.
363    #[must_use]
364    pub fn is_memory(&self) -> bool {
365        matches!(self, XmmMem::Mem(_))
366    }
367
368    /// Pretty-print the operand.
369    pub fn to_string(&self) -> String {
370        match self {
371            XmmMem::Xmm(xmm) => xmm.to_string(None),
372            XmmMem::Mem(amode) => amode.to_string(),
373        }
374    }
375
376    /// Pretty-print the operand in XED's dialect; see [`Amode`]'s `Display`.
377    pub fn to_string_xed(&self) -> String {
378        match self {
379            XmmMem::Xmm(xmm) => xmm.to_string(None),
380            XmmMem::Mem(amode) => alloc::format!("{amode:#}"),
381        }
382    }
383
384    /// Return the [`RexPrefix`] for each variant of this [`XmmMem`].
385    #[must_use]
386    pub(crate) fn as_rex_prefix(&self, enc_reg: u8, has_w_bit: bool, uses_8bit: bool) -> RexPrefix {
387        match self {
388            XmmMem::Xmm(rm) => RexPrefix::two_op(enc_reg, rm.enc(), has_w_bit, uses_8bit),
389            XmmMem::Mem(amode) => amode.as_rex_prefix(enc_reg, has_w_bit, uses_8bit),
390        }
391    }
392
393    /// Emit the ModR/M, SIB, and displacement suffixes for this [`XmmMem`].
394    pub(crate) fn encode_rex_suffixes(
395        &self,
396        sink: &mut impl CodeSink,
397        enc_reg: u8,
398        bytes_at_end: u8,
399        evex_scaling: Option<i8>,
400    ) {
401        match self {
402            XmmMem::Xmm(xmm) => {
403                sink.put1(encode_modrm(0b11, enc_reg & 0b111, xmm.enc() & 0b111));
404            }
405            XmmMem::Mem(amode) => {
406                amode.encode_rex_suffixes(sink, enc_reg, bytes_at_end, evex_scaling);
407            }
408        }
409    }
410
411    /// Return the registers for encoding the `b` and `x` bits (e.g., in a VEX
412    /// prefix).
413    ///
414    /// During encoding, the `b` bit is set by the topmost bit (the fourth bit)
415    /// of either the `reg` register or, if this is a memory address, the `base`
416    /// register. The `x` bit is set by the `index` register, when used.
417    pub(crate) fn encode_bx_regs(&self) -> (Option<u8>, Option<u8>) {
418        match self {
419            XmmMem::Xmm(reg) => (Some(reg.enc()), None),
420            XmmMem::Mem(amode) => amode.encode_bx_regs(),
421        }
422    }
423}
424
425impl<R: AsReg, M: AsReg> From<R> for XmmMem<R, M> {
426    fn from(reg: R) -> XmmMem<R, M> {
427        XmmMem::Xmm(reg)
428    }
429}
430
431impl<R: AsReg, M: AsReg> From<Amode<M>> for XmmMem<R, M> {
432    fn from(amode: Amode<M>) -> XmmMem<R, M> {
433        XmmMem::Mem(amode)
434    }
435}
436
437/// Emit the ModRM/SIB/displacement sequence for a memory operand.
438pub fn emit_modrm_sib_disp<R: AsReg>(
439    sink: &mut impl CodeSink,
440    enc_g: u8,
441    mem_e: &Amode<R>,
442    bytes_at_end: u8,
443    evex_scaling: Option<i8>,
444) {
445    match *mem_e {
446        Amode::ImmReg { simm32, base, .. } => {
447            let enc_e = base.enc();
448            let mut imm = Disp::new(simm32.value(sink), evex_scaling);
449
450            // Most base registers allow for a single ModRM byte plus an
451            // optional immediate. If rsp is the base register, however, then a
452            // SIB byte must be used.
453            let enc_e_low3 = enc_e & 7;
454            if enc_e_low3 == gpr::enc::RSP {
455                // Displacement from RSP is encoded with a SIB byte where
456                // the index and base are both encoded as RSP's encoding of
457                // 0b100. This special encoding means that the index register
458                // isn't used and the base is 0b100 with or without a
459                // REX-encoded 4th bit (e.g. rsp or r12)
460                sink.put1(encode_modrm(imm.m0d(), enc_g & 7, 0b100));
461                sink.put1(0b00_100_100);
462                imm.emit(sink);
463            } else {
464                // If the base register is rbp and there's no offset then force
465                // a 1-byte zero offset since otherwise the encoding would be
466                // invalid.
467                if enc_e_low3 == gpr::enc::RBP {
468                    imm.force_immediate();
469                }
470                sink.put1(encode_modrm(imm.m0d(), enc_g & 7, enc_e & 7));
471                imm.emit(sink);
472            }
473        }
474
475        Amode::ImmRegRegShift {
476            simm32,
477            base,
478            index,
479            scale,
480            ..
481        } => {
482            let enc_base = base.enc();
483            let enc_index = index.enc();
484
485            // Encoding of ModRM/SIB bytes don't allow the index register to
486            // ever be rsp. Note, though, that the encoding of r12, whose three
487            // lower bits match the encoding of rsp, is explicitly allowed with
488            // REX bytes so only rsp is disallowed.
489            assert!(enc_index != gpr::enc::RSP);
490
491            // If the offset is zero then there is no immediate. Note, though,
492            // that if the base register's lower three bits are `101` then an
493            // offset must be present. This is a special case in the encoding of
494            // the SIB byte and requires an explicit displacement with rbp/r13.
495            let mut imm = Disp::new(simm32.value(), evex_scaling);
496            if enc_base & 7 == gpr::enc::RBP {
497                imm.force_immediate();
498            }
499
500            // With the above determined encode the ModRM byte, then the SIB
501            // byte, then any immediate as necessary.
502            sink.put1(encode_modrm(imm.m0d(), enc_g & 7, 0b100));
503            sink.put1(encode_sib(scale.enc(), enc_index & 7, enc_base & 7));
504            imm.emit(sink);
505        }
506
507        Amode::RipRelative { target } => {
508            // RIP-relative is mod=00, rm=101.
509            sink.put1(encode_modrm(0b00, enc_g & 7, 0b101));
510
511            // Inform the code sink about the RIP-relative `target` at the
512            // current offset, emitting a `LabelUse`, a relocation, or etc as
513            // appropriate.
514            sink.use_target(target);
515
516            // N.B.: some instructions (XmmRmRImm format for example)
517            // have bytes *after* the RIP-relative offset. The
518            // addressed location is relative to the end of the
519            // instruction, but the relocation is nominally relative
520            // to the end of the u32 field. So, to compensate for
521            // this, we emit a negative extra offset in the u32 field
522            // initially, and the relocation will add to it.
523            sink.put4(-(i32::from(bytes_at_end)) as u32);
524        }
525    }
526}