cranelift_assembler_x64_meta/dsl.rs
1//! Defines a domain-specific language (DSL) for describing x64 instructions.
2//!
3//! This language is intended to be:
4//! - compact--i.e., define an x64 instruction on a single line, and
5//! - a close-to-direct mapping of what we read in the x64 reference manual.
6
7mod custom;
8mod encoding;
9mod features;
10pub mod format;
11
12pub use custom::{Custom, Customization};
13pub use encoding::{Encoding, ModRmKind, OpcodeMod};
14pub use encoding::{Evex, Length, Vex, VexEscape, VexPrefix, evex, vex};
15pub use encoding::{
16 Group1Prefix, Group2Prefix, Group3Prefix, Group4Prefix, Opcodes, Prefixes, Rex, TupleType, rex,
17};
18pub use features::{ALL_FEATURES, Feature, Features};
19pub use format::{Eflags, Extension, Format, Location, Mutability, Operand, OperandKind, RegClass};
20pub use format::{align, fmt, implicit, r, rw, sxl, sxq, sxw, w};
21
22/// Abbreviated constructor for an x64 instruction.
23pub fn inst(
24 mnemonic: impl Into<String>,
25 format: Format,
26 encoding: impl Into<Encoding>,
27 features: impl Into<Features>,
28) -> Inst {
29 let encoding = encoding.into();
30 encoding.validate(&format.operands);
31 Inst {
32 mnemonic: mnemonic.into(),
33 format,
34 encoding,
35 features: features.into(),
36 alternate: None,
37 has_trap: false,
38 custom: Custom::default(),
39 }
40}
41
42/// An x64 instruction.
43///
44/// Use [`inst`] to construct this within the
45/// [`instructions`](super::instructions) module. This structure is designed to
46/// represent all of the information for one instruction (a table row) in the
47/// x64 _Instruction Set Reference_ or at least enough to generate code to emit
48/// the instruction.
49pub struct Inst {
50 /// The instruction name as represented in the x64 reference manual. This is
51 /// the pretty-printed name used for disassembly. Multiple instructions may
52 /// have the same mnemonic, though; the combination of this field and the
53 /// format name must be unique (see [`Inst::name`]).
54 pub mnemonic: String,
55 /// The instruction operands, typically represented in the "Instruction"
56 /// column of the x64 reference manual.
57 pub format: Format,
58 /// The instruction encoding, typically represented in the "Opcode" column
59 /// of the x64 reference manual.
60 pub encoding: Encoding,
61 /// The CPU features required to use this instruction; this combines the
62 /// "64-bit/32-bit Mode Support" and "CPUID Feature Flag" columns of the x64
63 /// reference manual.
64 pub features: Features,
65 /// An alternate version of this instruction, if it exists.
66 pub alternate: Option<Alternate>,
67 /// Whether or not this instruction can trap and thus needs a `TrapCode`
68 /// payload in the instruction itself.
69 pub has_trap: bool,
70 /// Whether or not this instruction uses custom, external functions
71 /// instead of Rust code generated by this crate.
72 pub custom: Custom,
73}
74
75impl Inst {
76 /// The unique name for this instruction.
77 ///
78 /// To avoid ambiguity, this name combines the instruction mnemonic and the
79 /// format name in snake case. This is used in generated code to name the
80 /// instruction `struct` and builder functions.
81 ///
82 /// In rare cases, this `<mnemonic>_<format>` scheme does not uniquely
83 /// identify an instruction in x64 ISA (e.g., some extended versions,
84 /// VEX/EVEX). In these cases, we append a minimal identifier to
85 /// the format name (e.g., `sx*`) to keep this unique.
86 #[must_use]
87 pub fn name(&self) -> String {
88 format!(
89 "{}_{}",
90 self.mnemonic.to_lowercase(),
91 self.format.name.to_lowercase()
92 )
93 }
94
95 /// Flags this instruction as being able to trap, so needs a `TrapCode` at
96 /// compile time to track this.
97 pub fn has_trap(mut self) -> Self {
98 self.has_trap = true;
99 self
100 }
101
102 /// Indicate this instruction as needing custom processing.
103 pub fn custom(mut self, custom: impl Into<Custom>) -> Self {
104 self.custom = custom.into();
105 self
106 }
107
108 /// Sets the alternate version of this instruction, if it exists.
109 pub fn alt(mut self, feature: Feature, alternate: impl Into<String>) -> Self {
110 self.alternate = Some(Alternate {
111 feature,
112 name: alternate.into(),
113 });
114 self
115 }
116
117 /// The mnemonics XED prints for this encoding: `(register form, memory
118 /// form)`. XED picks a different spelling than we do in three systematic
119 /// ways, all handled here:
120 ///
121 /// 1. it drops the AT&T operand-size suffix we carry (`addl` -> `add`);
122 /// 2. it prefers a different condition-code alias (`cmovae` -> `cmovnb`);
123 /// 3. it appends a width marker when an operand is *memory*, sized by that
124 /// operand (`addsd` -> `addsdq`, `cmovnb` -> `cmovnbl`).
125 ///
126 /// The two forms differ only when (3) applies, which is why this returns a
127 /// pair: the choice is made at runtime from the `r/m` operand.
128 #[must_use]
129 pub fn xed_mnemonics(&self) -> (String, String) {
130 // `lock_addb` is printed `lock addb`; see `custom::mnemonic`.
131 let mut base = match self.mnemonic.strip_prefix("lock_") {
132 Some(rest) => format!("lock {rest}"),
133 None => self.mnemonic.clone(),
134 };
135
136 // (1) Drop our size suffix when it matches the width of some operand.
137 // Mnemonics XED spells differently outright keep their suffix (it is
138 // part of the name), as do condition-code mnemonics whose trailing
139 // letter is the condition itself (`setb`) rather than a size.
140 if !is_exception(&base) && !is_condition_family(&base) && !keeps_suffix(&base, &self.format)
141 {
142 let widths: Vec<_> = self
143 .format
144 .locations()
145 .map(|l| width_marker(l.bits()))
146 .collect();
147 if base.len() > 2 && widths.iter().any(|w| base.ends_with(w)) {
148 base.truncate(base.len() - 1);
149 }
150 }
151
152 // (2) XED's preferred spelling for the families that have aliases.
153 base = rename(&base);
154
155 // (3) A width marker is appended when the operand is memory, sized by
156 // that operand. `lea` names an address rather than accessing it, so
157 // never takes one; `push`/`pop` always do, even for registers; and for
158 // mnemonics that already name their width the marker repeats it
159 // (`pextrb` -> `pextrbb`).
160 if matches!(base.as_str(), "push" | "pop") {
161 let bits = self.format.locations().next().map_or(64, Location::bits);
162 let m = format!("{base}{}", width_marker(bits));
163 return (m.clone(), m);
164 }
165 let mem = match self.format.uses_memory() {
166 Some(_) if base == "lea" => base.clone(),
167 Some(_) if keeps_suffix(&base, &self.format) => {
168 // The mnemonic already names its width; XED repeats it using
169 // its own letter (`pextrd` is 32-bit, so `pextrdl`).
170 let named = match &base[base.len() - 1..] {
171 "b" => 8,
172 "w" => 16,
173 "d" => 32,
174 _ => 64,
175 };
176 format!("{base}{}", width_marker(named))
177 }
178 Some(loc) => format!("{base}{}", width_marker(loc.bits())),
179 None => base.clone(),
180 };
181 (base, mem)
182 }
183}
184
185/// XED's single-letter marker for an operand width.
186fn width_marker(bits: u16) -> &'static str {
187 match bits {
188 8 => "b",
189 16 => "w",
190 32 => "l",
191 64 => "q",
192 128 => "x",
193 256 => "y",
194 512 => "z",
195 b => unreachable!("no XED width marker for {b} bits"),
196 }
197}
198
199/// Mnemonics XED spells differently outright.
200const XED_RENAMES: &[(&str, &str)] = &[
201 ("cbtw", "data16 cbw"),
202 ("cltd", "cdq"),
203 ("cltq", "cdqe"),
204 ("cqto", "cqo"),
205 ("cwtd", "data16 cwd"),
206 ("cwtl", "cwde"),
207 ("movabsq", "mov"),
208 ("movslq", "movsxd"),
209];
210
211fn is_exception(m: &str) -> bool {
212 XED_RENAMES.iter().any(|(o, _)| *o == m)
213}
214
215/// The sixteen x64 condition codes, as spelled in our mnemonics.
216const CONDITIONS: &[&str] = &[
217 "a", "ae", "b", "be", "e", "g", "ge", "l", "le", "ne", "no", "np", "ns", "o", "p", "s",
218];
219
220/// Whether `m` is a `cmov`/`set`/`j` mnemonic carrying a valid condition code.
221fn is_condition_family(m: &str) -> bool {
222 ["cmov", "set", "j"]
223 .iter()
224 .filter_map(|p| m.strip_prefix(p))
225 .any(|cc| CONDITIONS.contains(&cc))
226}
227
228/// Mnemonics whose trailing size letter names the instruction (`pextrb`
229/// extracts a *byte*) rather than decorating it in the AT&T style.
230fn keeps_suffix(m: &str, format: &Format) -> bool {
231 const INTRINSIC: &str =
232 "pextr pinsr vpextr vpinsr vpbroadcast pmovsxd pmovzxd vpmovsxd vpmovzxd";
233 INTRINSIC.split(' ').any(|p| m.starts_with(p))
234 // `movq` is a GPR move (suffix) or an XMM move (name) by operand type.
235 || (matches!(m, "movq" | "vmovq") && format.locations().any(|l| l.bits() == 128))
236}
237
238/// Rewrite a suffix-stripped mnemonic into XED's preferred spelling.
239fn rename(base: &str) -> String {
240 if let Some((_, x)) = XED_RENAMES.iter().find(|(o, _)| *o == base) {
241 return (*x).to_string();
242 }
243
244 // `movzbl`/`movswq`/... collapse to plain `movzx`/`movsx`; after the size
245 // suffix is stripped a single source-width letter remains.
246 for (prefix, xed) in [("movz", "movzx"), ("movs", "movsx")] {
247 if let Some(rest) = base.strip_prefix(prefix) {
248 if rest.len() == 1 && "bwlq".contains(rest) {
249 return xed.to_string();
250 }
251 }
252 }
253
254 // Condition-code families: XED prefers the negated spelling for six of the
255 // sixteen conditions.
256 for prefix in ["cmov", "set", "j"] {
257 if let Some(cc) = base.strip_prefix(prefix) {
258 let renamed = match cc {
259 "a" => "nbe",
260 "ae" => "nb",
261 "e" => "z",
262 "g" => "nle",
263 "ge" => "nl",
264 "ne" => "nz",
265 _ => return base.to_string(),
266 };
267 return format!("{prefix}{renamed}");
268 }
269 }
270
271 base.to_string()
272}
273
274impl core::fmt::Display for Inst {
275 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
276 let Inst {
277 mnemonic: name,
278 format,
279 encoding,
280 features,
281 alternate,
282 has_trap,
283 custom,
284 } = self;
285 write!(f, "{name}: {format} => {encoding}")?;
286 write!(f, " [{features}]")?;
287 if let Some(alternate) = alternate {
288 write!(f, " (alternate: {alternate})")?;
289 }
290 if *has_trap {
291 write!(f, " has_trap")?;
292 }
293 if !custom.is_empty() {
294 write!(f, " custom({custom})")?;
295 }
296 Ok(())
297 }
298}
299
300/// An alternate version of an instruction.
301///
302/// Some AVX-specific context: some instructions have the same semantics in
303/// their SSE and AVX encodings. In these cases, we use this structure to record
304/// the name of the upgraded version of the instruction, allowing us to replace
305/// the SSE instruction with its AVX version during lowering. For AVX, using the
306/// VEX-encoded instruction is typically better than its legacy SSE version:
307/// - VEX can encode three operands
308/// - VEX allows unaligned memory access (avoids additional `MOVUPS`)
309/// - VEX can compact byte-long prefixes into the VEX prefix
310/// - VEX instructions zero the upper bits of XMM registers by default
311pub struct Alternate {
312 /// Indicate the feature check to use to trigger the replacement.
313 pub feature: Feature,
314 /// The full name (see [`Inst::name`]) of the instruction used for
315 /// replacement.
316 pub name: String,
317}
318
319impl core::fmt::Display for Alternate {
320 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
321 write!(f, "{} => {}", self.feature, self.name)
322 }
323}