cranelift_assembler_x64/
mem.rs

1//! Memory operands to instructions.
2
3use crate::api::{AsReg, CodeSink, Constant, KnownOffset, KnownOffsetTable, Label, TrapCode};
4use crate::gpr::{self, NonRspGpr, Size};
5use crate::rex::{encode_modrm, encode_sib, Imm, RexFlags};
6use crate::{RegisterVisitor, Registers};
7
8/// x64 memory addressing modes.
9#[derive(Clone, Debug)]
10#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
11pub enum Amode<R: AsReg> {
12    ImmReg {
13        base: R,
14        simm32: AmodeOffsetPlusKnownOffset,
15        trap: Option<TrapCode>,
16    },
17    ImmRegRegShift {
18        base: R,
19        index: NonRspGpr<R>,
20        scale: Scale,
21        simm32: AmodeOffset,
22        trap: Option<TrapCode>,
23    },
24    RipRelative {
25        target: DeferredTarget,
26    },
27}
28
29impl<R: AsReg> Amode<R> {
30    /// Return the [`TrapCode`] associated with this [`Amode`], if any.
31    pub fn trap_code(&self) -> Option<TrapCode> {
32        match self {
33            Amode::ImmReg { trap, .. } | Amode::ImmRegRegShift { trap, .. } => *trap,
34            Amode::RipRelative { .. } => None,
35        }
36    }
37
38    /// Encode the [`Amode`] into a ModRM/SIB/displacement sequence.
39    pub fn emit_rex_prefix(&self, rex: RexFlags, enc_g: u8, sink: &mut impl CodeSink) {
40        match self {
41            Amode::ImmReg { base, .. } => {
42                let enc_e = base.enc();
43                rex.emit_two_op(sink, enc_g, enc_e);
44            }
45            Amode::ImmRegRegShift { base, index, .. } => {
46                let enc_base = base.enc();
47                let enc_index = index.enc();
48                rex.emit_three_op(sink, enc_g, enc_index, enc_base);
49            }
50            Amode::RipRelative { .. } => {
51                // note REX.B = 0.
52                rex.emit_two_op(sink, enc_g, 0);
53            }
54        }
55    }
56}
57
58/// Visit the registers in an [`Amode`].
59///
60/// This is helpful for generated code: it allows capturing the `R::ReadGpr`
61/// type (which an `Amode` method cannot) and simplifies the code to be
62/// generated.
63pub(crate) fn visit_amode<R: Registers>(
64    amode: &mut Amode<R::ReadGpr>,
65    visitor: &mut impl RegisterVisitor<R>,
66) {
67    match amode {
68        Amode::ImmReg { base, .. } => {
69            visitor.read_gpr(base);
70        }
71        Amode::ImmRegRegShift { base, index, .. } => {
72            visitor.read_gpr(base);
73            visitor.read_gpr(index.as_mut());
74        }
75        Amode::RipRelative { .. } => {}
76    }
77}
78
79/// A 32-bit immediate for address offsets.
80#[derive(Clone, Copy, Debug)]
81#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
82pub struct AmodeOffset(i32);
83
84impl AmodeOffset {
85    #[must_use]
86    pub fn new(value: i32) -> Self {
87        Self(value)
88    }
89
90    #[must_use]
91    pub fn value(self) -> i32 {
92        self.0
93    }
94}
95
96impl From<i32> for AmodeOffset {
97    fn from(value: i32) -> Self {
98        Self(value)
99    }
100}
101
102impl std::fmt::LowerHex for AmodeOffset {
103    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
104        // This rather complex implementation is necessary to match how
105        // `capstone` pretty-prints memory immediates.
106        if self.0 == 0 {
107            return Ok(());
108        }
109        if self.0 < 0 {
110            write!(f, "-")?;
111        }
112        if self.0 > 9 || self.0 < -9 {
113            write!(f, "0x")?;
114        }
115        let abs = match self.0.checked_abs() {
116            Some(i) => i,
117            None => -2_147_483_648,
118        };
119        std::fmt::LowerHex::fmt(&abs, f)
120    }
121}
122
123/// An [`AmodeOffset`] immediate with an optional known offset.
124///
125/// Cranelift does not know certain offsets until emission time. To accommodate
126/// Cranelift, this structure stores an optional [`KnownOffset`]. The following
127/// happens immediately before emission:
128/// - the [`KnownOffset`] is looked up, mapping it to an offset value
129/// - the [`Simm32`] value is added to the offset value
130#[derive(Clone, Debug)]
131pub struct AmodeOffsetPlusKnownOffset {
132    pub simm32: AmodeOffset,
133    pub offset: Option<KnownOffset>,
134}
135
136impl AmodeOffsetPlusKnownOffset {
137    /// # Panics
138    ///
139    /// Panics if the sum of the immediate and the known offset value overflows.
140    #[must_use]
141    pub fn value(&self, offsets: &impl KnownOffsetTable) -> i32 {
142        let known_offset = match self.offset {
143            Some(offset) => offsets[offset],
144            None => 0,
145        };
146        known_offset
147            .checked_add(self.simm32.value())
148            .expect("no wrapping")
149    }
150}
151
152impl std::fmt::LowerHex for AmodeOffsetPlusKnownOffset {
153    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
154        if let Some(offset) = self.offset {
155            write!(f, "<offset:{offset}>+")?;
156        }
157        std::fmt::LowerHex::fmt(&self.simm32, f)
158    }
159}
160
161/// For RIP-relative addressing, keep track of the [`CodeSink`]-specific target.
162#[derive(Clone, Debug)]
163#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
164pub enum DeferredTarget {
165    Label(Label),
166    Constant(Constant),
167}
168
169impl<R: AsReg> std::fmt::Display for Amode<R> {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        let pointer_width = Size::Quadword;
172        match self {
173            Amode::ImmReg { simm32, base, .. } => {
174                // Note: size is always 8; the address is 64 bits,
175                // even if the addressed operand is smaller.
176                let base = base.to_string(Some(pointer_width));
177                write!(f, "{simm32:x}({base})")
178            }
179            Amode::ImmRegRegShift {
180                simm32,
181                base,
182                index,
183                scale,
184                ..
185            } => {
186                let base = base.to_string(Some(pointer_width));
187                let index = index.to_string(pointer_width);
188                let shift = scale.shift();
189                if shift > 1 {
190                    write!(f, "{simm32:x}({base}, {index}, {shift})")
191                } else {
192                    write!(f, "{simm32:x}({base}, {index})")
193                }
194            }
195            Amode::RipRelative { .. } => write!(f, "(%rip)"),
196        }
197    }
198}
199
200/// The scaling factor for the index register in certain [`Amode`]s.
201#[derive(Clone, Debug)]
202#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
203pub enum Scale {
204    One,
205    Two,
206    Four,
207    Eight,
208}
209
210impl Scale {
211    /// Create a new [`Scale`] from its hardware encoding.
212    ///
213    /// # Panics
214    ///
215    /// Panics if `enc` is not a valid encoding for a scale (0-3).
216    #[must_use]
217    pub fn new(enc: u8) -> Self {
218        match enc {
219            0b00 => Scale::One,
220            0b01 => Scale::Two,
221            0b10 => Scale::Four,
222            0b11 => Scale::Eight,
223            _ => panic!("invalid scale encoding: {enc}"),
224        }
225    }
226
227    /// Return the hardware encoding of this [`Scale`].
228    fn enc(&self) -> u8 {
229        match self {
230            Scale::One => 0b00,
231            Scale::Two => 0b01,
232            Scale::Four => 0b10,
233            Scale::Eight => 0b11,
234        }
235    }
236
237    /// Return how much this [`Scale`] will shift the value in the index
238    /// register of the SIB byte.
239    ///
240    /// This is useful for pretty-printing; when encoding, one usually needs
241    /// [`Scale::enc`].
242    fn shift(&self) -> u8 {
243        1 << self.enc()
244    }
245}
246
247/// A general-purpose register or memory operand.
248#[derive(Clone, Debug)]
249#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
250#[allow(
251    clippy::module_name_repetitions,
252    reason = "'GprMem' indicates this has GPR and memory variants"
253)]
254pub enum GprMem<R: AsReg, M: AsReg> {
255    Gpr(R),
256    Mem(Amode<M>),
257}
258
259impl<R: AsReg, M: AsReg> GprMem<R, M> {
260    /// Pretty-print the operand.
261    pub fn to_string(&self, size: Size) -> String {
262        match self {
263            GprMem::Gpr(gpr) => gpr.to_string(Some(size)),
264            GprMem::Mem(amode) => amode.to_string(),
265        }
266    }
267
268    /// Proxy on the 8-bit REX flag emission; helpful for simplifying generated
269    /// code.
270    pub(crate) fn always_emit_if_8bit_needed(&self, rex: &mut RexFlags) {
271        match self {
272            GprMem::Gpr(gpr) => {
273                rex.always_emit_if_8bit_needed(gpr.enc());
274            }
275            GprMem::Mem(_) => {}
276        }
277    }
278}
279
280/// An XMM register or memory operand.
281#[derive(Clone, Debug)]
282#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
283#[allow(
284    clippy::module_name_repetitions,
285    reason = "'XmmMem' indicates this has Xmm and memory variants"
286)]
287pub enum XmmMem<R: AsReg, M: AsReg> {
288    Xmm(R),
289    Mem(Amode<M>),
290}
291
292impl<R: AsReg, M: AsReg> XmmMem<R, M> {
293    /// Pretty-print the operand.
294    pub fn to_string(&self) -> String {
295        match self {
296            XmmMem::Xmm(xmm) => xmm.to_string(None),
297            XmmMem::Mem(amode) => amode.to_string(),
298        }
299    }
300}
301
302/// Emit the ModRM/SIB/displacement sequence for a memory operand.
303pub fn emit_modrm_sib_disp<R: AsReg>(
304    sink: &mut impl CodeSink,
305    offsets: &impl KnownOffsetTable,
306    enc_g: u8,
307    mem_e: &Amode<R>,
308    bytes_at_end: u8,
309    evex_scaling: Option<i8>,
310) {
311    match mem_e.clone() {
312        Amode::ImmReg { simm32, base, .. } => {
313            let enc_e = base.enc();
314            let mut imm = Imm::new(simm32.value(offsets), evex_scaling);
315
316            // Most base registers allow for a single ModRM byte plus an
317            // optional immediate. If rsp is the base register, however, then a
318            // SIB byte must be used.
319            let enc_e_low3 = enc_e & 7;
320            if enc_e_low3 == gpr::enc::RSP {
321                // Displacement from RSP is encoded with a SIB byte where
322                // the index and base are both encoded as RSP's encoding of
323                // 0b100. This special encoding means that the index register
324                // isn't used and the base is 0b100 with or without a
325                // REX-encoded 4th bit (e.g. rsp or r12)
326                sink.put1(encode_modrm(imm.m0d(), enc_g & 7, 0b100));
327                sink.put1(0b00_100_100);
328                imm.emit(sink);
329            } else {
330                // If the base register is rbp and there's no offset then force
331                // a 1-byte zero offset since otherwise the encoding would be
332                // invalid.
333                if enc_e_low3 == gpr::enc::RBP {
334                    imm.force_immediate();
335                }
336                sink.put1(encode_modrm(imm.m0d(), enc_g & 7, enc_e & 7));
337                imm.emit(sink);
338            }
339        }
340
341        Amode::ImmRegRegShift {
342            simm32,
343            base,
344            index,
345            scale,
346            ..
347        } => {
348            let enc_base = base.enc();
349            let enc_index = index.enc();
350
351            // Encoding of ModRM/SIB bytes don't allow the index register to
352            // ever be rsp. Note, though, that the encoding of r12, whose three
353            // lower bits match the encoding of rsp, is explicitly allowed with
354            // REX bytes so only rsp is disallowed.
355            assert!(enc_index != gpr::enc::RSP);
356
357            // If the offset is zero then there is no immediate. Note, though,
358            // that if the base register's lower three bits are `101` then an
359            // offset must be present. This is a special case in the encoding of
360            // the SIB byte and requires an explicit displacement with rbp/r13.
361            let mut imm = Imm::new(simm32.value(), evex_scaling);
362            if enc_base & 7 == gpr::enc::RBP {
363                imm.force_immediate();
364            }
365
366            // With the above determined encode the ModRM byte, then the SIB
367            // byte, then any immediate as necessary.
368            sink.put1(encode_modrm(imm.m0d(), enc_g & 7, 0b100));
369            sink.put1(encode_sib(scale.enc(), enc_index & 7, enc_base & 7));
370            imm.emit(sink);
371        }
372
373        Amode::RipRelative { target } => {
374            // RIP-relative is mod=00, rm=101.
375            sink.put1(encode_modrm(0b00, enc_g & 7, 0b101));
376
377            let offset = sink.current_offset();
378            let target = match target {
379                DeferredTarget::Label(label) => label.clone(),
380                DeferredTarget::Constant(constant) => sink.get_label_for_constant(constant.clone()),
381            };
382            sink.use_label_at_offset(offset, target);
383
384            // N.B.: some instructions (XmmRmRImm format for example)
385            // have bytes *after* the RIP-relative offset. The
386            // addressed location is relative to the end of the
387            // instruction, but the relocation is nominally relative
388            // to the end of the u32 field. So, to compensate for
389            // this, we emit a negative extra offset in the u32 field
390            // initially, and the relocation will add to it.
391            sink.put4(-(i32::from(bytes_at_end)) as u32);
392        }
393    }
394}