1 //! Generate the Cranelift-specific integration of the x64 assembler.
2 
3 use cranelift_assembler_x64_meta::dsl::{Format, Inst, Mutability, Operand, OperandKind};
4 use cranelift_srcgen::{fmtln, Formatter};
5 
6 /// Returns the Rust type used for the `IsleConstructorRaw` variants.
7 pub fn rust_param_raw(op: &Operand) -> String {
8     match op.location.kind() {
9         OperandKind::Imm(loc) => {
10             let bits = loc.bits();
11             if op.extension.is_sign_extended() {
12                 format!("i{bits}")
13             } else {
14                 format!("u{bits}")
15             }
16         }
17         OperandKind::RegMem(rm) => {
18             let reg = match rm.bits() {
19                 128 => "Xmm",
20                 _ => "Gpr",
21             };
22             let aligned = if op.align { "Aligned" } else { "" };
23             format!("&{reg}Mem{aligned}")
24         }
25         OperandKind::Mem(_) => {
26             format!("&Amode")
27         }
28         OperandKind::Reg(r) => match r.bits() {
29             128 => "Xmm".to_string(),
30             _ => "Gpr".to_string(),
31         },
32         OperandKind::FixedReg(_) => "Gpr".to_string(),
33     }
34 }
35 
36 /// Returns the conversion function, if any, when converting the ISLE type for
37 /// this parameter to the assembler type for this parameter. Effectively
38 /// converts `self.rust_param_raw()` to the assembler type.
39 pub fn rust_convert_isle_to_assembler(op: &Operand) -> Option<&'static str> {
40     match op.location.kind() {
41         OperandKind::Reg(r) => Some(match (r.bits(), op.mutability) {
42             (128, Mutability::Read) => "cranelift_assembler_x64::Xmm::new",
43             (128, Mutability::ReadWrite) => "self.convert_xmm_to_assembler_read_write_xmm",
44             (_, Mutability::Read) => "cranelift_assembler_x64::Gpr::new",
45             (_, Mutability::ReadWrite) => "self.convert_gpr_to_assembler_read_write_gpr",
46         }),
47         OperandKind::Mem(_) => Some("self.convert_amode_to_assembler_amode"),
48         OperandKind::RegMem(r) => Some(match (r.bits(), op.mutability) {
49             (128, Mutability::Read) => "self.convert_xmm_mem_to_assembler_read_xmm_mem",
50             (128, Mutability::ReadWrite) => "self.convert_xmm_mem_to_assembler_read_write_xmm_mem",
51             (_, Mutability::Read) => "self.convert_gpr_mem_to_assembler_read_gpr_mem",
52             (_, Mutability::ReadWrite) => "self.convert_gpr_mem_to_assembler_read_write_gpr_mem",
53         }),
54         OperandKind::Imm(loc) => match (op.extension.is_sign_extended(), loc.bits()) {
55             (true, 8) => Some("cranelift_assembler_x64::Simm8::new"),
56             (true, 16) => Some("cranelift_assembler_x64::Simm16::new"),
57             (true, 32) => Some("cranelift_assembler_x64::Simm32::new"),
58             (false, 8) => Some("cranelift_assembler_x64::Imm8::new"),
59             (false, 16) => Some("cranelift_assembler_x64::Imm16::new"),
60             (false, 32) => Some("cranelift_assembler_x64::Imm32::new"),
61             _ => None,
62         },
63         OperandKind::FixedReg(_) => None,
64     }
65 }
66 
67 /// `fn x64_<inst>(&mut self, <params>) -> Inst<R> { ... }`
68 ///
69 /// # Panics
70 ///
71 /// This function panics if the instruction has no operands.
72 pub fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) {
73     let struct_name = inst.name();
74     let params = inst
75         .format
76         .operands
77         .iter()
78         .filter(|o| o.mutability.is_read())
79         // FIXME(#10238) don't filter out fixed regs here
80         .filter(|o| !matches!(o.location.kind(), OperandKind::FixedReg(_)))
81         .collect::<Vec<_>>();
82     let results = inst
83         .format
84         .operands
85         .iter()
86         .filter(|o| o.mutability.is_write())
87         .collect::<Vec<_>>();
88     let rust_params = params
89         .iter()
90         .map(|o| format!("{}: {}", o.location, rust_param_raw(o)))
91         .collect::<Vec<_>>()
92         .join(", ");
93     f.add_block(
94         &format!("fn x64_{struct_name}_raw(&mut self, {rust_params}) -> AssemblerOutputs"),
95         |f| {
96             for o in params.iter() {
97                 let l = o.location;
98                 match rust_convert_isle_to_assembler(o) {
99                     Some(cvt) => fmtln!(f, "let {l} = {cvt}({l});"),
100                     None => fmtln!(f, "let {l} = {l}.clone();"),
101                 }
102             }
103             let args = params
104                 .iter()
105                 .map(|o| format!("{}.clone()", o.location))
106                 .collect::<Vec<_>>();
107             let args = args.join(", ");
108             fmtln!(
109                 f,
110                 "let inst = cranelift_assembler_x64::inst::{struct_name}::new({args}).into();"
111             );
112             if let Some(OperandKind::FixedReg(_)) = results.first().map(|o| o.location.kind()) {
113                 fmtln!(f, "#[allow(unused_variables, reason = \"FIXME(#10238): fixed register instructions have TODOs\")]");
114             }
115             fmtln!(f, "let inst = MInst::External {{ inst }};");
116 
117             use cranelift_assembler_x64_meta::dsl::Mutability::*;
118             match results.as_slice() {
119                 [] => fmtln!(f, "SideEffectNoResult::Inst(inst)"),
120                 [one] => match one.mutability {
121                     Read => unreachable!(),
122                     ReadWrite => match one.location.kind() {
123                         OperandKind::Imm(_) => unreachable!(),
124                         // FIXME(#10238)
125                         OperandKind::FixedReg(_) => fmtln!(f, "todo!()"),
126                         // One read/write register output? Output the instruction
127                         // and that register.
128                         OperandKind::Reg(r) => {
129                             let (var, ty) = match r.bits() {
130                                 128 => ("xmm", "Xmm"),
131                                 _ => ("gpr", "Gpr"),
132                             };
133                             fmtln!(
134                                 f,
135                                 "let {var} = {r}.as_ref().write.to_reg();",
136                             );
137                             fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}");
138                         },
139                         // One read/write memory operand? Output a side effect.
140                         OperandKind::Mem(_) => {
141                             fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }}")
142                         }
143                         // One read/write regmem output? We need to output
144                         // everything and it'll internally disambiguate which was
145                         // emitted (e.g. the mem variant or the register variant).
146                         OperandKind::RegMem(rm) => {
147                             assert_eq!(results.len(), 1);
148                             let (var, ty) = match rm.bits() {
149                                 128 => ("xmm", "Xmm"),
150                                 _ => ("gpr", "Gpr"),
151                             };
152                             f.add_block(&format!("match {rm}"), |f| {
153                                 f.add_block(&format!("asm::{ty}Mem::{ty}(reg) => "), |f| {
154                                     fmtln!(f, "let {var} = reg.write.to_reg();");
155                                     fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }} ");
156                                 });
157                                 f.add_block(&format!("asm::{ty}Mem::Mem(_) => "), |f| {
158                                     fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }} ");
159                                 });
160                             });
161                         }
162                     },
163                 },
164                 _ => panic!("instruction has more than one result"),
165             }
166         },
167     );
168 }
169 
170 /// Generate the `isle_assembler_methods!` macro.
171 pub fn generate_rust_macro(f: &mut Formatter, insts: &[Inst]) {
172     fmtln!(f, "#[doc(hidden)]");
173     fmtln!(f, "macro_rules! isle_assembler_methods {{");
174     f.indent(|f| {
175         fmtln!(f, "() => {{");
176         f.indent(|f| {
177             for inst in insts {
178                 generate_macro_inst_fn(f, inst);
179             }
180         });
181         fmtln!(f, "}};");
182     });
183     fmtln!(f, "}}");
184 }
185 
186 /// Returns the type of this operand in ISLE as a part of the ISLE "raw"
187 /// constructors.
188 pub fn isle_param_raw(op: &Operand) -> String {
189     match op.location.kind() {
190         OperandKind::Imm(loc) => {
191             let bits = loc.bits();
192             if op.extension.is_sign_extended() {
193                 format!("i{bits}")
194             } else {
195                 format!("u{bits}")
196             }
197         }
198         OperandKind::Reg(r) => match r.bits() {
199             128 => "Xmm".to_string(),
200             _ => "Gpr".to_string(),
201         },
202         OperandKind::FixedReg(_) => "Gpr".to_string(),
203         OperandKind::Mem(_) => {
204             if op.align {
205                 unimplemented!("no way yet to mark an Amode as aligned")
206             } else {
207                 "Amode".to_string()
208             }
209         }
210         OperandKind::RegMem(rm) => {
211             let reg = match rm.bits() {
212                 128 => "Xmm",
213                 _ => "Gpr",
214             };
215             let aligned = if op.align { "Aligned" } else { "" };
216             format!("{reg}Mem{aligned}")
217         }
218     }
219 }
220 
221 /// Different kinds of ISLE constructors generated for a particular instruction.
222 ///
223 /// One instruction may generate a single constructor or multiple constructors.
224 /// For example an instruction that writes its result to a register will
225 /// generate only a single constructor. An instruction where the destination
226 /// read/write operand is `GprMem` will generate two constructors though, one
227 /// for memory and one for in registers.
228 #[derive(Copy, Clone, Debug)]
229 pub enum IsleConstructor {
230     /// This constructor only produces a side effect, meaning that the
231     /// instruction does not produce results in registers. This may produce
232     /// a result in memory, however.
233     RetMemorySideEffect,
234 
235     /// This constructor produces a `Gpr` value, meaning that it will write the
236     /// result to a `Gpr`.
237     RetGpr,
238 
239     /// This constructor produces an `Xmm` value, meaning that it will write the
240     /// result to an `Xmm`.
241     RetXmm,
242 }
243 
244 impl IsleConstructor {
245     /// Returns the result type, in ISLE, that this constructor generates.
246     pub fn result_ty(&self) -> &'static str {
247         match self {
248             IsleConstructor::RetMemorySideEffect => "SideEffectNoResult",
249             IsleConstructor::RetGpr => "Gpr",
250             IsleConstructor::RetXmm => "Xmm",
251         }
252     }
253 
254     /// Returns the constructor used to convert an `AssemblerOutput` into the
255     /// type returned by [`Self::result_ty`].
256     pub fn conversion_constructor(&self) -> &'static str {
257         match self {
258             IsleConstructor::RetMemorySideEffect => "defer_side_effect",
259             IsleConstructor::RetGpr => "emit_ret_gpr",
260             IsleConstructor::RetXmm => "emit_ret_xmm",
261         }
262     }
263 
264     /// Returns the suffix used in the ISLE constructor name.
265     pub fn suffix(&self) -> &'static str {
266         match self {
267             IsleConstructor::RetMemorySideEffect => "_mem",
268             IsleConstructor::RetGpr => "",
269             IsleConstructor::RetXmm => "",
270         }
271     }
272 }
273 
274 /// Returns the parameter type used for the `IsleConstructor` variant
275 /// provided.
276 pub fn isle_param_for_ctor(op: &Operand, ctor: IsleConstructor) -> String {
277     match op.location.kind() {
278         // Writable `RegMem` operands are special here: in one constructor
279         // it's operating on memory so the argument is `Amode` and in the
280         // other constructor it's operating on registers so the argument is
281         // a `Gpr`.
282         OperandKind::RegMem(_) if op.mutability.is_write() => match ctor {
283             IsleConstructor::RetMemorySideEffect => "Amode".to_string(),
284             IsleConstructor::RetGpr => "Gpr".to_string(),
285             IsleConstructor::RetXmm => "Xmm".to_string(),
286         },
287 
288         // everything else is the same as the "raw" variant
289         _ => isle_param_raw(op),
290     }
291 }
292 
293 /// Returns the ISLE constructors that are going to be used when generating
294 /// this instruction.
295 ///
296 /// Note that one instruction might need multiple constructors, such as one
297 /// for operating on memory and one for operating on registers.
298 pub fn isle_constructors(format: &Format) -> Vec<IsleConstructor> {
299     use Mutability::*;
300     use OperandKind::*;
301 
302     let write_operands = format
303         .operands
304         .iter()
305         .filter(|o| o.mutability.is_write())
306         .collect::<Vec<_>>();
307     match &write_operands[..] {
308             [] => unimplemented!("if you truly need this (and not a `SideEffect*`), add a `NoReturn` variant to `AssemblerOutputs`"),
309             [one] => match one.mutability {
310                 Read => unreachable!(),
311                 ReadWrite => match one.location.kind() {
312                     Imm(_) => unreachable!(),
313                     FixedReg(_) => vec![IsleConstructor::RetGpr],
314                     // One read/write register output? Output the instruction
315                     // and that register.
316                     Reg(r) => match r.bits() {
317                         128 => vec![IsleConstructor::RetXmm],
318                         _ => vec![IsleConstructor::RetGpr],
319                     },
320                     // One read/write memory operand? Output a side effect.
321                     Mem(_) => vec![IsleConstructor::RetMemorySideEffect],
322                     // One read/write reg-mem output? We need constructors for
323                     // both variants.
324                     RegMem(rm) => match rm.bits() {
325                         128 => vec![IsleConstructor::RetXmm, IsleConstructor::RetMemorySideEffect],
326                         _ => vec![IsleConstructor::RetGpr, IsleConstructor::RetMemorySideEffect],
327                     },
328                 }
329             },
330             other => panic!("unsupported number of write operands {other:?}"),
331         }
332 }
333 
334 /// Generate a "raw" constructor that simply constructs, but does not emit
335 /// the assembly instruction:
336 ///
337 /// ```text
338 /// (decl x64_<inst>_raw (<params>) AssemblerOutputs)
339 /// (extern constructor x64_<inst>_raw x64_<inst>_raw)
340 /// ```
341 ///
342 /// Using the "raw" constructor, we also generate "emitter" constructors
343 /// (see [`IsleConstructor`]). E.g., instructions that write to a register
344 /// will return the register:
345 ///
346 /// ```text
347 /// (decl x64_<inst> (<params>) Gpr)
348 /// (rule (x64_<inst> <params>) (emit_ret_gpr (x64_<inst>_raw <params>)))
349 /// ```
350 ///
351 /// For instructions that write to memory, we also generate an "emitter"
352 /// constructor with the `_mem` suffix:
353 ///
354 /// ```text
355 /// (decl x64_<inst>_mem (<params>) SideEffectNoResult)
356 /// (rule (x64_<inst>_mem <params>) (defer_side_effect (x64_<inst>_raw <params>)))
357 /// ```
358 ///
359 /// # Panics
360 ///
361 /// This function panics if the instruction has no operands.
362 pub fn generate_isle_inst_decls(f: &mut Formatter, inst: &Inst) {
363     // First declare the "raw" constructor which is implemented in Rust
364     // with `generate_isle_macro` above. This is an "extern" constructor
365     // with relatively raw types. This is not intended to be used by
366     // general lowering rules in ISLE.
367     let struct_name = inst.name();
368     let raw_name = format!("x64_{struct_name}_raw");
369     let params = inst
370         .format
371         .operands
372         .iter()
373         .filter(|o| o.mutability.is_read())
374         // FIXME(#10238) don't filter out fixed regs here
375         .filter(|o| !matches!(o.location.kind(), OperandKind::FixedReg(_)))
376         .collect::<Vec<_>>();
377     let raw_param_tys = params
378         .iter()
379         .map(|o| isle_param_raw(o))
380         .collect::<Vec<_>>()
381         .join(" ");
382     fmtln!(f, "(decl {raw_name} ({raw_param_tys}) AssemblerOutputs)");
383     fmtln!(f, "(extern constructor {raw_name} {raw_name})");
384 
385     // Next, for each "emitter" ISLE constructor being generated, synthesize
386     // a pure-ISLE constructor which delegates appropriately to the `*_raw`
387     // constructor above.
388     //
389     // The main purpose of these constructors is to have faithful type
390     // signatures for the SSA nature of VCode/ISLE, effectively translating
391     // x64's type system to ISLE/VCode's type system.
392     for ctor in isle_constructors(&inst.format) {
393         let suffix = ctor.suffix();
394         let rule_name = format!("x64_{struct_name}{suffix}");
395         let result_ty = ctor.result_ty();
396         let param_tys = params
397             .iter()
398             .map(|o| isle_param_for_ctor(o, ctor))
399             .collect::<Vec<_>>()
400             .join(" ");
401         let param_names = params
402             .iter()
403             .map(|o| o.location.to_string())
404             .collect::<Vec<_>>()
405             .join(" ");
406         let convert = ctor.conversion_constructor();
407 
408         fmtln!(f, "(decl {rule_name} ({param_tys}) {result_ty})");
409         fmtln!(
410             f,
411             "(rule ({rule_name} {param_names}) ({convert} ({raw_name} {param_names})))"
412         );
413     }
414 }
415 
416 /// Generate the ISLE definitions that match the `isle_assembler_methods!` macro
417 /// above.
418 pub fn generate_isle(f: &mut Formatter, insts: &[Inst]) {
419     fmtln!(f, "(type AssemblerOutputs (enum");
420     fmtln!(f, "    ;; Used for instructions that have ISLE");
421     fmtln!(f, "    ;; `SideEffect`s (memory stores, traps,");
422     fmtln!(f, "    ;; etc.) and do not return a `Value`.");
423     fmtln!(f, "    (SideEffect (inst MInst))");
424     fmtln!(f, "    ;; Used for instructions that return a");
425     fmtln!(f, "    ;; GPR (including `GprMem` variants with");
426     fmtln!(f, "    ;; a GPR as the first argument).");
427     fmtln!(f, "    (RetGpr (inst MInst) (gpr Gpr))");
428     fmtln!(f, "    ;; Used for instructions that return an");
429     fmtln!(f, "    ;; XMM register.");
430     fmtln!(f, "    (RetXmm (inst MInst) (xmm Xmm))");
431     fmtln!(f, "    ;; TODO: eventually add more variants for");
432     fmtln!(f, "    ;; multi-return, XMM, etc.; see");
433     fmtln!(
434         f,
435         "    ;; https://github.com/bytecodealliance/wasmtime/pull/10276"
436     );
437     fmtln!(f, "))");
438     f.empty_line();
439 
440     fmtln!(f, ";; Directly emit instructions that return a GPR.");
441     fmtln!(f, "(decl emit_ret_gpr (AssemblerOutputs) Gpr)");
442     fmtln!(f, "(rule (emit_ret_gpr (AssemblerOutputs.RetGpr inst gpr))");
443     fmtln!(f, "    (let ((_ Unit (emit inst))) gpr))");
444     f.empty_line();
445 
446     fmtln!(f, ";; Directly emit instructions that return an");
447     fmtln!(f, ";; XMM register.");
448     fmtln!(f, "(decl emit_ret_xmm (AssemblerOutputs) Xmm)");
449     fmtln!(f, "(rule (emit_ret_xmm (AssemblerOutputs.RetXmm inst xmm))");
450     fmtln!(f, "    (let ((_ Unit (emit inst))) xmm))");
451     f.empty_line();
452 
453     fmtln!(f, ";; Pass along the side-effecting instruction");
454     fmtln!(f, ";; for later emission.");
455     fmtln!(
456         f,
457         "(decl defer_side_effect (AssemblerOutputs) SideEffectNoResult)"
458     );
459     fmtln!(
460         f,
461         "(rule (defer_side_effect (AssemblerOutputs.SideEffect inst))"
462     );
463     fmtln!(f, "    (SideEffectNoResult.Inst inst))");
464     f.empty_line();
465 
466     for inst in insts {
467         generate_isle_inst_decls(f, inst);
468         f.empty_line();
469     }
470 }
471