Skip to content

Commit c213302

Browse files
authored
asm: refactor how REX suffixes are emitted (#10749)
This change simplifies the generated code for encoding REX suffixes (ModR/M byte, SIB byte, displacement) but does not alter the underlying logic. As a part of this refactoring, the displacement struct is now appropriately named `Disp` and several public functions no longer need to be so.
1 parent 19000ee commit c213302

File tree

4 files changed

+84
-87
lines changed

4 files changed

+84
-87
lines changed

cranelift/assembler-x64/meta/src/generate/format.rs

+10-56
Original file line numberDiff line numberDiff line change
@@ -136,64 +136,18 @@ impl dsl::Format {
136136
[FixedReg(_), Imm(_)] => {
137137
// No need to emit a ModRM byte: we know the register used.
138138
}
139-
[Mem(dst), Imm(_)] => {
140-
let digit = rex
141-
.digit
142-
.expect("REX digit must be set for operands: [RegMem, Imm]");
139+
[Mem(mem), Imm(_)] | [RegMem(mem), Imm(_)] => {
140+
let digit = rex.digit.unwrap();
143141
fmtln!(f, "let digit = 0x{digit:x};");
144-
fmtln!(
145-
f,
146-
"emit_modrm_sib_disp(buf, off, digit, &self.{dst}, 0, None);"
147-
);
142+
fmtln!(f, "self.{mem}.encode_rex_suffixes(buf, off, digit, 0);");
148143
}
149-
[RegMem(dst), Imm(_)] => {
150-
let digit = rex
151-
.digit
152-
.expect("REX digit must be set for operands: [RegMem, Imm]");
153-
fmtln!(f, "let digit = 0x{digit:x};");
154-
f.add_block(&format!("match &self.{dst}"), |f| {
155-
fmtln!(f, "GprMem::Gpr({dst}) => emit_modrm(buf, digit, {dst}.enc()),");
156-
fmtln!(f, "GprMem::Mem({dst}) => emit_modrm_sib_disp(buf, off, digit, {dst}, 0, None),");
157-
});
158-
}
159-
[Reg(dst), RegMem(src)] => {
160-
fmtln!(f, "let {dst} = self.{dst}.enc();");
161-
f.add_block(&format!("match &self.{src}"), |f| {
162-
match dst.bits() {
163-
128 => {
164-
fmtln!(f, "XmmMem::Xmm({src}) => emit_modrm(buf, {dst}, {src}.enc()),");
165-
fmtln!(f, "XmmMem::Mem({src}) => emit_modrm_sib_disp(buf, off, {dst}, {src}, 0, None),");
166-
}
167-
_ => {
168-
fmtln!(f, "GprMem::Gpr({src}) => emit_modrm(buf, {dst}, {src}.enc()),");
169-
fmtln!(f, "GprMem::Mem({src}) => emit_modrm_sib_disp(buf, off, {dst}, {src}, 0, None),");
170-
}
171-
};
172-
});
173-
}
174-
[Mem(dst), Reg(src)] => {
175-
fmtln!(f, "let {src} = self.{src}.enc();");
176-
fmtln!(
177-
f,
178-
"emit_modrm_sib_disp(buf, off, {src}, &self.{dst}, 0, None);"
179-
);
180-
}
181-
[RegMem(dst), Reg(src)]
182-
| [RegMem(dst), Reg(src), Imm(_)]
183-
| [RegMem(dst), Reg(src), FixedReg(_)] => {
184-
fmtln!(f, "let {src} = self.{src}.enc();");
185-
f.add_block(&format!("match &self.{dst}"), |f| {
186-
match src.bits() {
187-
128 => {
188-
fmtln!(f, "XmmMem::Xmm({dst}) => emit_modrm(buf, {src}, {dst}.enc()),");
189-
fmtln!(f, "XmmMem::Mem({dst}) => emit_modrm_sib_disp(buf, off, {src}, {dst}, 0, None),");
190-
}
191-
_ => {
192-
fmtln!(f, "GprMem::Gpr({dst}) => emit_modrm(buf, {src}, {dst}.enc()),");
193-
fmtln!(f, "GprMem::Mem({dst}) => emit_modrm_sib_disp(buf, off, {src}, {dst}, 0, None),");
194-
}
195-
};
196-
});
144+
[Reg(reg), RegMem(mem)]
145+
| [Mem(mem), Reg(reg)]
146+
| [RegMem(mem), Reg(reg)]
147+
| [RegMem(mem), Reg(reg), Imm(_)]
148+
| [RegMem(mem), Reg(reg), FixedReg(_)] => {
149+
fmtln!(f, "let reg = self.{reg}.enc();");
150+
fmtln!(f, "self.{mem}.encode_rex_suffixes(buf, off, reg, 0);");
197151
}
198152
unknown => unimplemented!("unknown pattern: {unknown:?}"),
199153
}

cranelift/assembler-x64/src/inst.rs

+2-8
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,10 @@
66
use crate::api::{AsReg, CodeSink, KnownOffsetTable, RegisterVisitor, Registers};
77
use crate::gpr::{self, Gpr, Size};
88
use crate::imm::{Extension, Imm16, Imm32, Imm8, Simm32, Simm8};
9-
use crate::mem::{emit_modrm_sib_disp, visit_amode, Amode, GprMem, XmmMem};
10-
use crate::rex::{self, RexPrefix};
9+
use crate::mem::{visit_amode, Amode, GprMem, XmmMem};
10+
use crate::rex::RexPrefix;
1111
use crate::xmm::Xmm;
1212
use crate::Fixed;
1313

1414
// Include code generated by the `meta` crate.
1515
include!(concat!(env!("OUT_DIR"), "/assembler.rs"));
16-
17-
/// Helper function to make code generation simpler.
18-
fn emit_modrm(buffer: &mut impl CodeSink, enc_reg_g: u8, rm_e: u8) {
19-
let modrm = rex::encode_modrm(0b11, enc_reg_g & 7, rm_e & 7);
20-
buffer.put1(modrm);
21-
}

cranelift/assembler-x64/src/mem.rs

+52-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::api::{AsReg, CodeSink, Constant, KnownOffset, KnownOffsetTable, Label, TrapCode};
44
use crate::gpr::{self, NonRspGpr, Size};
5-
use crate::rex::{encode_modrm, encode_sib, Imm, RexPrefix};
5+
use crate::rex::{encode_modrm, encode_sib, Disp, RexPrefix};
66
use crate::{RegisterVisitor, Registers};
77

88
/// x64 memory addressing modes.
@@ -48,6 +48,18 @@ impl<R: AsReg> Amode<R> {
4848
Amode::RipRelative { .. } => RexPrefix::two_op(enc_reg, 0, has_w_bit, uses_8bit),
4949
}
5050
}
51+
52+
/// Emit the ModR/M, SIB, and displacement suffixes as neeeded for this
53+
/// `Amode`.
54+
pub(crate) fn encode_rex_suffixes(
55+
&self,
56+
sink: &mut impl CodeSink,
57+
offsets: &impl KnownOffsetTable,
58+
enc_reg: u8,
59+
bytes_at_end: u8,
60+
) {
61+
emit_modrm_sib_disp(sink, offsets, enc_reg, self, bytes_at_end, None);
62+
}
5163
}
5264

5365
/// Visit the registers in an [`Amode`].
@@ -268,6 +280,24 @@ impl<R: AsReg, M: AsReg> GprMem<R, M> {
268280
GprMem::Mem(amode) => amode.as_rex_prefix(enc_reg, has_w_bit, uses_8bit),
269281
}
270282
}
283+
284+
/// Emit the ModR/M, SIB, and displacement suffixes for this [`GprMem`].
285+
pub(crate) fn encode_rex_suffixes(
286+
&self,
287+
sink: &mut impl CodeSink,
288+
offsets: &impl KnownOffsetTable,
289+
enc_reg: u8,
290+
bytes_at_end: u8,
291+
) {
292+
match self {
293+
GprMem::Gpr(gpr) => {
294+
sink.put1(encode_modrm(0b11, enc_reg & 0b111, gpr.enc() & 0b111));
295+
}
296+
GprMem::Mem(amode) => {
297+
amode.encode_rex_suffixes(sink, offsets, enc_reg, bytes_at_end);
298+
}
299+
}
300+
}
271301
}
272302

273303
/// An XMM register or memory operand.
@@ -299,10 +329,28 @@ impl<R: AsReg, M: AsReg> XmmMem<R, M> {
299329
XmmMem::Mem(amode) => amode.as_rex_prefix(enc_reg, has_w_bit, uses_8bit),
300330
}
301331
}
332+
333+
/// Emit the ModR/M, SIB, and displacement suffixes for this [`XmmMem`].
334+
pub(crate) fn encode_rex_suffixes(
335+
&self,
336+
sink: &mut impl CodeSink,
337+
offsets: &impl KnownOffsetTable,
338+
enc_reg: u8,
339+
bytes_at_end: u8,
340+
) {
341+
match self {
342+
XmmMem::Xmm(xmm) => {
343+
sink.put1(encode_modrm(0b11, enc_reg & 0b111, xmm.enc() & 0b111));
344+
}
345+
XmmMem::Mem(amode) => {
346+
amode.encode_rex_suffixes(sink, offsets, enc_reg, bytes_at_end);
347+
}
348+
}
349+
}
302350
}
303351

304352
/// Emit the ModRM/SIB/displacement sequence for a memory operand.
305-
pub fn emit_modrm_sib_disp<R: AsReg>(
353+
fn emit_modrm_sib_disp<R: AsReg>(
306354
sink: &mut impl CodeSink,
307355
offsets: &impl KnownOffsetTable,
308356
enc_g: u8,
@@ -313,7 +361,7 @@ pub fn emit_modrm_sib_disp<R: AsReg>(
313361
match mem_e.clone() {
314362
Amode::ImmReg { simm32, base, .. } => {
315363
let enc_e = base.enc();
316-
let mut imm = Imm::new(simm32.value(offsets), evex_scaling);
364+
let mut imm = Disp::new(simm32.value(offsets), evex_scaling);
317365

318366
// Most base registers allow for a single ModRM byte plus an
319367
// optional immediate. If rsp is the base register, however, then a
@@ -360,7 +408,7 @@ pub fn emit_modrm_sib_disp<R: AsReg>(
360408
// that if the base register's lower three bits are `101` then an
361409
// offset must be present. This is a special case in the encoding of
362410
// the SIB byte and requires an explicit displacement with rbp/r13.
363-
let mut imm = Imm::new(simm32.value(), evex_scaling);
411+
let mut imm = Disp::new(simm32.value(), evex_scaling);
364412
if enc_base & 7 == gpr::enc::RBP {
365413
imm.force_immediate();
366414
}

cranelift/assembler-x64/src/rex.rs

+20-19
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
33
use crate::api::CodeSink;
44

5-
pub(crate) fn low8_will_sign_extend_to_32(xs: i32) -> bool {
5+
fn low8_will_sign_extend_to_32(xs: i32) -> bool {
66
xs == ((xs << 24) >> 24)
77
}
88

99
/// Encode the ModR/M byte.
1010
#[inline]
11-
pub fn encode_modrm(m0d: u8, enc_reg_g: u8, rm_e: u8) -> u8 {
11+
pub(crate) fn encode_modrm(m0d: u8, enc_reg_g: u8, rm_e: u8) -> u8 {
1212
debug_assert!(m0d < 4);
1313
debug_assert!(enc_reg_g < 8);
1414
debug_assert!(rm_e < 8);
@@ -17,7 +17,7 @@ pub fn encode_modrm(m0d: u8, enc_reg_g: u8, rm_e: u8) -> u8 {
1717

1818
/// Encode the SIB byte (scale-index-base).
1919
#[inline]
20-
pub fn encode_sib(scale: u8, enc_index: u8, enc_base: u8) -> u8 {
20+
pub(crate) fn encode_sib(scale: u8, enc_index: u8, enc_base: u8) -> u8 {
2121
debug_assert!(scale < 4);
2222
debug_assert!(enc_index < 8);
2323
debug_assert!(enc_base < 8);
@@ -136,14 +136,15 @@ impl RexPrefix {
136136
}
137137
}
138138

139+
/// The displacement bytes used after the ModR/M and SIB bytes.
139140
#[derive(Copy, Clone)]
140-
pub enum Imm {
141+
pub enum Disp {
141142
None,
142143
Imm8(i8),
143144
Imm32(i32),
144145
}
145146

146-
impl Imm {
147+
impl Disp {
147148
/// Classifies the 32-bit immediate `val` as how this can be encoded
148149
/// with ModRM/SIB bytes.
149150
///
@@ -157,51 +158,51 @@ impl Imm {
157158
/// The `evex_scaling` factor provided here is `Some(N)` for EVEX
158159
/// instructions. This is taken into account where the `Imm` value
159160
/// contained is the raw byte offset.
160-
pub fn new(val: i32, evex_scaling: Option<i8>) -> Imm {
161+
pub fn new(val: i32, evex_scaling: Option<i8>) -> Disp {
161162
if val == 0 {
162-
return Imm::None;
163+
return Disp::None;
163164
}
164165
match evex_scaling {
165166
Some(scaling) => {
166167
if val % i32::from(scaling) == 0 {
167168
let scaled = val / i32::from(scaling);
168169
if low8_will_sign_extend_to_32(scaled) {
169-
return Imm::Imm8(scaled as i8);
170+
return Disp::Imm8(scaled as i8);
170171
}
171172
}
172-
Imm::Imm32(val)
173+
Disp::Imm32(val)
173174
}
174175
None => match i8::try_from(val) {
175-
Ok(val) => Imm::Imm8(val),
176-
Err(_) => Imm::Imm32(val),
176+
Ok(val) => Disp::Imm8(val),
177+
Err(_) => Disp::Imm32(val),
177178
},
178179
}
179180
}
180181

181182
/// Forces `Imm::None` to become `Imm::Imm8(0)`, used for special cases
182183
/// where some base registers require an immediate.
183184
pub fn force_immediate(&mut self) {
184-
if let Imm::None = self {
185-
*self = Imm::Imm8(0);
185+
if let Disp::None = self {
186+
*self = Disp::Imm8(0);
186187
}
187188
}
188189

189190
/// Returns the two "mod" bits present at the upper bits of the mod/rm
190191
/// byte.
191192
pub fn m0d(self) -> u8 {
192193
match self {
193-
Imm::None => 0b00,
194-
Imm::Imm8(_) => 0b01,
195-
Imm::Imm32(_) => 0b10,
194+
Disp::None => 0b00,
195+
Disp::Imm8(_) => 0b01,
196+
Disp::Imm32(_) => 0b10,
196197
}
197198
}
198199

199200
/// Emit the truncated immediate into the code sink.
200201
pub fn emit(self, sink: &mut impl CodeSink) {
201202
match self {
202-
Imm::None => {}
203-
Imm::Imm8(n) => sink.put1(n as u8),
204-
Imm::Imm32(n) => sink.put4(n as u32),
203+
Disp::None => {}
204+
Disp::Imm8(n) => sink.put1(n as u8),
205+
Disp::Imm32(n) => sink.put4(n as u32),
205206
}
206207
}
207208
}

0 commit comments

Comments
 (0)