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