cranelift_assembler_x64/rex.rs
1//! Encoding logic for REX instructions.
2
3use crate::api::CodeSink;
4
5fn low8_will_sign_extend_to_32(xs: i32) -> bool {
6 xs == ((xs << 24) >> 24)
7}
8
9/// Encode the ModR/M byte.
10#[inline]
11pub(crate) fn encode_modrm(m0d: u8, enc_reg_g: u8, rm_e: u8) -> u8 {
12 debug_assert!(m0d < 4);
13 debug_assert!(enc_reg_g < 8);
14 debug_assert!(rm_e < 8);
15 ((m0d & 3) << 6) | ((enc_reg_g & 7) << 3) | (rm_e & 7)
16}
17
18/// Encode the SIB byte (scale-index-base).
19#[inline]
20pub(crate) fn encode_sib(scale: u8, enc_index: u8, enc_base: u8) -> u8 {
21 debug_assert!(scale < 4);
22 debug_assert!(enc_index < 8);
23 debug_assert!(enc_base < 8);
24 ((scale & 3) << 6) | ((enc_index & 7) << 3) | (enc_base & 7)
25}
26
27/// Tests whether `enc` is `rsp`, `rbp`, `rsi`, or `rdi`. If 8-bit register
28/// sizes are used then it means a REX prefix is required.
29///
30/// This function is used below in combination with `uses_8bit` booleans to
31/// determine the `RexPrefix::must_emit` flag. Table 3-2 in volume 1 of the
32/// Intel manual details how referencing `dil`, the low 8-bits of `rdi`,
33/// requires the use of the REX prefix as without it it would otherwise
34/// reference the `AH` register.
35///
36/// This is used whenever a register is encoded with a `RexPrefix` and is also
37/// only used if the register is referenced in its 8-bit form. That means for
38/// example that when encoding addressing modes this function is not used.
39/// Addressing modes use 64-bit versions of registers meaning that the 8-bit
40/// special case does not apply.
41const fn is_special_if_8bit(enc: u8) -> bool {
42 enc >= 4 && enc <= 7
43}
44
45/// Construct and emit the REX prefix byte.
46///
47/// For more details, see section 2.2.1, "REX Prefixes" in Intel's reference
48/// manual.
49#[derive(Clone, Copy)]
50pub struct RexPrefix {
51 byte: u8,
52 must_emit: bool,
53}
54
55impl RexPrefix {
56 /// Construct the [`RexPrefix`] for a unary instruction.
57 ///
58 /// Used with a single register operand:
59 /// - `x` and `r` are unused.
60 /// - `b` extends the `reg` register, allowing access to r8-r15, or the top
61 /// bit of the opcode digit.
62 #[inline]
63 #[must_use]
64 pub const fn one_op(enc: u8, w_bit: bool, uses_8bit: bool) -> Self {
65 let must_emit = uses_8bit && is_special_if_8bit(enc);
66 let w = if w_bit { 1 } else { 0 };
67 let r = 0;
68 let x = 0;
69 let b = (enc >> 3) & 1;
70 let flag = 0x40 | (w << 3) | (r << 2) | (x << 1) | b;
71 Self {
72 byte: flag,
73 must_emit,
74 }
75 }
76
77 /// Construct the [`RexPrefix`] for a binary instruction.
78 ///
79 /// Used without a SIB byte or for register-to-register addressing:
80 /// - `r` extends the `reg` operand, allowing access to r8-r15.
81 /// - `x` is unused.
82 /// - `b` extends the `r/m` operand, allowing access to r8-r15.
83 #[inline]
84 #[must_use]
85 pub const fn two_op(enc_reg: u8, enc_rm: u8, w_bit: bool, uses_8bit: bool) -> Self {
86 let mut ret = RexPrefix::mem_op(enc_reg, enc_rm, w_bit, uses_8bit);
87 if uses_8bit && is_special_if_8bit(enc_rm) {
88 ret.must_emit = true;
89 }
90 ret
91 }
92
93 /// Construct the [`RexPrefix`] for a binary instruction where one operand
94 /// is a memory address.
95 ///
96 /// This is the same as [`RexPrefix::two_op`] except that `enc_rm` is
97 /// guaranteed to address a 64-bit register. This has a slightly different
98 /// meaning when `uses_8bit` is `true` to omit the REX prefix in more cases
99 /// than `two_op` would emit.
100 #[inline]
101 #[must_use]
102 pub const fn mem_op(enc_reg: u8, enc_rm: u8, w_bit: bool, uses_8bit: bool) -> Self {
103 let must_emit = uses_8bit && is_special_if_8bit(enc_reg);
104 let w = if w_bit { 1 } else { 0 };
105 let r = (enc_reg >> 3) & 1;
106 let x = 0;
107 let b = (enc_rm >> 3) & 1;
108 let flag = 0x40 | (w << 3) | (r << 2) | (x << 1) | b;
109 Self {
110 byte: flag,
111 must_emit,
112 }
113 }
114
115 /// Construct the [`RexPrefix`] for an instruction using an opcode digit.
116 ///
117 /// :
118 /// - `r` extends the opcode digit.
119 /// - `x` is unused.
120 /// - `b` extends the `reg` operand, allowing access to r8-r15.
121 #[inline]
122 #[must_use]
123 pub const fn with_digit(digit: u8, enc_reg: u8, w_bit: bool, uses_8bit: bool) -> Self {
124 Self::two_op(digit, enc_reg, w_bit, uses_8bit)
125 }
126
127 /// Construct the [`RexPrefix`] for a ternary instruction, typically using a
128 /// memory address.
129 ///
130 /// Used with a SIB byte:
131 /// - `r` extends the `reg` operand, allowing access to r8-r15.
132 /// - `x` extends the index register, allowing access to r8-r15.
133 /// - `b` extends the base register, allowing access to r8-r15.
134 #[inline]
135 #[must_use]
136 pub const fn three_op(
137 enc_reg: u8,
138 enc_index: u8,
139 enc_base: u8,
140 w_bit: bool,
141 uses_8bit: bool,
142 ) -> Self {
143 let must_emit = uses_8bit && is_special_if_8bit(enc_reg);
144 let w = if w_bit { 1 } else { 0 };
145 let r = (enc_reg >> 3) & 1;
146 let x = (enc_index >> 3) & 1;
147 let b = (enc_base >> 3) & 1;
148 let flag = 0x40 | (w << 3) | (r << 2) | (x << 1) | b;
149 Self {
150 byte: flag,
151 must_emit,
152 }
153 }
154
155 /// Possibly emit the REX prefix byte.
156 ///
157 /// This will only be emitted if the REX prefix is not `0x40` (the default)
158 /// or if the instruction uses 8-bit operands.
159 #[inline]
160 pub fn encode(&self, sink: &mut impl CodeSink) {
161 if self.byte != 0x40 || self.must_emit {
162 sink.put1(self.byte);
163 }
164 }
165}
166
167/// The displacement bytes used after the ModR/M and SIB bytes.
168#[derive(Copy, Clone)]
169pub enum Disp {
170 None,
171 Imm8(i8),
172 Imm32(i32),
173}
174
175impl Disp {
176 /// Classifies the 32-bit immediate `val` as how this can be encoded
177 /// with ModRM/SIB bytes.
178 ///
179 /// For `evex_scaling` according to Section 2.7.5 of Intel's manual:
180 ///
181 /// > EVEX-encoded instructions always use a compressed displacement scheme
182 /// > by multiplying disp8 in conjunction with a scaling factor N that is
183 /// > determined based on the vector length, the value of EVEX.b bit
184 /// > (embedded broadcast) and the input element size of the instruction
185 ///
186 /// The `evex_scaling` factor provided here is `Some(N)` for EVEX
187 /// instructions. This is taken into account where the `Imm` value
188 /// contained is the raw byte offset.
189 pub fn new(val: i32, evex_scaling: Option<i8>) -> Disp {
190 if val == 0 {
191 return Disp::None;
192 }
193 match evex_scaling {
194 Some(scaling) => {
195 if val % i32::from(scaling) == 0 {
196 let scaled = val / i32::from(scaling);
197 if low8_will_sign_extend_to_32(scaled) {
198 return Disp::Imm8(scaled as i8);
199 }
200 }
201 Disp::Imm32(val)
202 }
203 None => match i8::try_from(val) {
204 Ok(val) => Disp::Imm8(val),
205 Err(_) => Disp::Imm32(val),
206 },
207 }
208 }
209
210 /// Forces `Imm::None` to become `Imm::Imm8(0)`, used for special cases
211 /// where some base registers require an immediate.
212 pub fn force_immediate(&mut self) {
213 if let Disp::None = self {
214 *self = Disp::Imm8(0);
215 }
216 }
217
218 /// Returns the two "mod" bits present at the upper bits of the mod/rm
219 /// byte.
220 pub fn m0d(self) -> u8 {
221 match self {
222 Disp::None => 0b00,
223 Disp::Imm8(_) => 0b01,
224 Disp::Imm32(_) => 0b10,
225 }
226 }
227
228 /// Emit the truncated immediate into the code sink.
229 pub fn emit(self, sink: &mut impl CodeSink) {
230 match self {
231 Disp::None => {}
232 Disp::Imm8(n) => sink.put1(n as u8),
233 Disp::Imm32(n) => sink.put4(n as u32),
234 }
235 }
236}