cranelift_assembler_x64/
mem.rs

1//! Memory operands to instructions.
2
3use crate::api::{AsReg, CodeSink, Constant, KnownOffsetTable, Label, TrapCode};
4use crate::imm::{Simm32, Simm32PlusKnownOffset};
5use crate::reg::{self, NonRspGpr, Size};
6use crate::rex::{encode_modrm, encode_sib, Imm, RexFlags};
7
8/// x64 memory addressing modes.
9#[derive(Clone, Debug)]
10#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
11pub enum Amode<R: AsReg> {
12    ImmReg {
13        base: R,
14        simm32: Simm32PlusKnownOffset,
15        trap: Option<TrapCode>,
16    },
17    ImmRegRegShift {
18        base: R,
19        index: NonRspGpr<R>,
20        scale: Scale,
21        simm32: Simm32,
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    /// Return the registers used by this [`Amode`].
58    ///
59    /// This is useful in generated code to allow access by a
60    /// [`RegisterVisitor`](crate::RegisterVisitor).
61    pub fn registers_mut(&mut self) -> Vec<&mut R> {
62        match self {
63            Amode::ImmReg { base, .. } => {
64                vec![base]
65            }
66            Amode::ImmRegRegShift { base, index, .. } => {
67                vec![base, index.as_mut()]
68            }
69            Amode::RipRelative { .. } => {
70                vec![]
71            }
72        }
73    }
74}
75
76/// For RIP-relative addressing, keep track of the [`CodeSink`]-specific target.
77#[derive(Clone, Debug)]
78#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
79pub enum DeferredTarget {
80    Label(Label),
81    Constant(Constant),
82}
83
84impl<R: AsReg> std::fmt::Display for Amode<R> {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        match self {
87            Amode::ImmReg { simm32, base, .. } => {
88                // Note: size is always 8; the address is 64 bits,
89                // even if the addressed operand is smaller.
90                let base = reg::enc::to_string(base.enc(), Size::Quadword);
91                write!(f, "{simm32:x}({base})")
92            }
93            Amode::ImmRegRegShift {
94                simm32,
95                base,
96                index,
97                scale,
98                ..
99            } => {
100                let base = reg::enc::to_string(base.enc(), Size::Quadword);
101                let index = reg::enc::to_string(index.enc(), Size::Quadword);
102                let shift = scale.shift();
103                if shift > 1 {
104                    write!(f, "{simm32:x}({base}, {index}, {shift})")
105                } else {
106                    write!(f, "{simm32:x}({base}, {index})")
107                }
108            }
109            Amode::RipRelative { .. } => write!(f, "(%rip)"),
110        }
111    }
112}
113
114/// The scaling factor for the index register in certain [`Amode`]s.
115#[derive(Clone, Debug)]
116#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
117pub enum Scale {
118    One,
119    Two,
120    Four,
121    Eight,
122}
123
124impl Scale {
125    /// Create a new [`Scale`] from its hardware encoding.
126    ///
127    /// # Panics
128    ///
129    /// Panics if `enc` is not a valid encoding for a scale (0-3).
130    #[must_use]
131    pub fn new(enc: u8) -> Self {
132        match enc {
133            0b00 => Scale::One,
134            0b01 => Scale::Two,
135            0b10 => Scale::Four,
136            0b11 => Scale::Eight,
137            _ => panic!("invalid scale encoding: {enc}"),
138        }
139    }
140
141    /// Return the hardware encoding of this [`Scale`].
142    fn enc(&self) -> u8 {
143        match self {
144            Scale::One => 0b00,
145            Scale::Two => 0b01,
146            Scale::Four => 0b10,
147            Scale::Eight => 0b11,
148        }
149    }
150
151    /// Return how much this [`Scale`] will shift the value in the index
152    /// register of the SIB byte.
153    ///
154    /// This is useful for pretty-printing; when encoding, one usually needs
155    /// [`Scale::enc`].
156    fn shift(&self) -> u8 {
157        1 << self.enc()
158    }
159}
160
161/// A general-purpose register or memory operand.
162#[derive(Clone, Debug)]
163#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
164#[allow(clippy::module_name_repetitions)]
165pub enum GprMem<R: AsReg, M: AsReg> {
166    Gpr(R),
167    Mem(Amode<M>),
168}
169
170impl<R: AsReg, M: AsReg> GprMem<R, M> {
171    /// Pretty-print the operand.
172    pub fn to_string(&self, size: Size) -> String {
173        match self {
174            GprMem::Gpr(gpr) => reg::enc::to_string(gpr.enc(), size).to_owned(),
175            GprMem::Mem(amode) => amode.to_string(),
176        }
177    }
178
179    /// Proxy on the 8-bit REX flag emission; helpful for simplifying generated
180    /// code.
181    pub(crate) fn always_emit_if_8bit_needed(&self, rex: &mut RexFlags) {
182        match self {
183            GprMem::Gpr(gpr) => {
184                rex.always_emit_if_8bit_needed(gpr.enc());
185            }
186            GprMem::Mem(_) => {}
187        }
188    }
189}
190
191/// Emit the ModRM/SIB/displacement sequence for a memory operand.
192pub fn emit_modrm_sib_disp<R: AsReg>(
193    sink: &mut impl CodeSink,
194    offsets: &impl KnownOffsetTable,
195    enc_g: u8,
196    mem_e: &Amode<R>,
197    bytes_at_end: u8,
198    evex_scaling: Option<i8>,
199) {
200    match mem_e.clone() {
201        Amode::ImmReg { simm32, base, .. } => {
202            let enc_e = base.enc();
203            let mut imm = Imm::new(simm32.value(offsets), evex_scaling);
204
205            // Most base registers allow for a single ModRM byte plus an
206            // optional immediate. If rsp is the base register, however, then a
207            // SIB byte must be used.
208            let enc_e_low3 = enc_e & 7;
209            if enc_e_low3 == reg::enc::RSP {
210                // Displacement from RSP is encoded with a SIB byte where
211                // the index and base are both encoded as RSP's encoding of
212                // 0b100. This special encoding means that the index register
213                // isn't used and the base is 0b100 with or without a
214                // REX-encoded 4th bit (e.g. rsp or r12)
215                sink.put1(encode_modrm(imm.m0d(), enc_g & 7, 0b100));
216                sink.put1(0b00_100_100);
217                imm.emit(sink);
218            } else {
219                // If the base register is rbp and there's no offset then force
220                // a 1-byte zero offset since otherwise the encoding would be
221                // invalid.
222                if enc_e_low3 == reg::enc::RBP {
223                    imm.force_immediate();
224                }
225                sink.put1(encode_modrm(imm.m0d(), enc_g & 7, enc_e & 7));
226                imm.emit(sink);
227            }
228        }
229
230        Amode::ImmRegRegShift {
231            simm32,
232            base,
233            index,
234            scale,
235            ..
236        } => {
237            let enc_base = base.enc();
238            let enc_index = index.enc();
239
240            // Encoding of ModRM/SIB bytes don't allow the index register to
241            // ever be rsp. Note, though, that the encoding of r12, whose three
242            // lower bits match the encoding of rsp, is explicitly allowed with
243            // REX bytes so only rsp is disallowed.
244            assert!(enc_index != reg::enc::RSP);
245
246            // If the offset is zero then there is no immediate. Note, though,
247            // that if the base register's lower three bits are `101` then an
248            // offset must be present. This is a special case in the encoding of
249            // the SIB byte and requires an explicit displacement with rbp/r13.
250            let mut imm = Imm::new(simm32.value(), evex_scaling);
251            if enc_base & 7 == reg::enc::RBP {
252                imm.force_immediate();
253            }
254
255            // With the above determined encode the ModRM byte, then the SIB
256            // byte, then any immediate as necessary.
257            sink.put1(encode_modrm(imm.m0d(), enc_g & 7, 0b100));
258            sink.put1(encode_sib(scale.enc(), enc_index & 7, enc_base & 7));
259            imm.emit(sink);
260        }
261
262        Amode::RipRelative { target } => {
263            // RIP-relative is mod=00, rm=101.
264            sink.put1(encode_modrm(0b00, enc_g & 7, 0b101));
265
266            let offset = sink.current_offset();
267            let target = match target {
268                DeferredTarget::Label(label) => label.clone(),
269                DeferredTarget::Constant(constant) => sink.get_label_for_constant(constant.clone()),
270            };
271            sink.use_label_at_offset(offset, target);
272
273            // N.B.: some instructions (XmmRmRImm format for example)
274            // have bytes *after* the RIP-relative offset. The
275            // addressed location is relative to the end of the
276            // instruction, but the relocation is nominally relative
277            // to the end of the u32 field. So, to compensate for
278            // this, we emit a negative extra offset in the u32 field
279            // initially, and the relocation will add to it.
280            #[allow(clippy::cast_sign_loss)]
281            sink.put4(-(i32::from(bytes_at_end)) as u32);
282        }
283    }
284}