1 //! Generate the Cranelift-specific integration of the x64 assembler.
2 
3 use cranelift_assembler_x64_meta::dsl::{
4     Format, Inst, Location, Mutability, Operand, OperandKind, RegClass,
5 };
6 use cranelift_srcgen::{Formatter, fmtln};
7 
8 /// This factors out use of the assembler crate name.
9 const ASM: &str = "cranelift_assembler_x64";
10 
11 /// Returns the Rust type used for the `IsleConstructorRaw` variants.
12 pub fn rust_param_raw(op: &Operand) -> String {
13     match op.location.kind() {
14         OperandKind::Imm(loc) => {
15             let bits = loc.bits();
16             if op.extension.is_sign_extended() {
17                 format!("i{bits}")
18             } else {
19                 format!("u{bits}")
20             }
21         }
22         OperandKind::RegMem(rm) => {
23             let reg = rm.reg_class().unwrap();
24             let aligned = if op.align { "Aligned" } else { "" };
25             format!("&{reg}Mem{aligned}")
26         }
27         OperandKind::Mem(_) => {
28             format!("&SyntheticAmode")
29         }
30         OperandKind::Reg(r) | OperandKind::FixedReg(r) => r.reg_class().unwrap().to_string(),
31     }
32 }
33 
34 /// Returns the conversion function, if any, when converting the ISLE type for
35 /// this parameter to the assembler type for this parameter. Effectively
36 /// converts `self.rust_param_raw()` to the assembler type.
37 pub fn rust_convert_isle_to_assembler(op: &Operand) -> String {
38     match op.location.kind() {
39         OperandKind::Imm(loc) => {
40             let bits = loc.bits();
41             let ty = if op.extension.is_sign_extended() {
42                 "Simm"
43             } else {
44                 "Imm"
45             };
46             format!("{ASM}::{ty}{bits}::new({loc})")
47         }
48         OperandKind::FixedReg(r) => {
49             let reg = r.reg_class().unwrap().to_string().to_lowercase();
50             match op.mutability {
51                 Mutability::Read => format!("{ASM}::Fixed({r})"),
52                 Mutability::Write => {
53                     format!("{ASM}::Fixed(self.temp_writable_{reg}())")
54                 }
55                 Mutability::ReadWrite => {
56                     format!("self.convert_{reg}_to_assembler_fixed_read_write_{reg}({r})")
57                 }
58             }
59         }
60         OperandKind::Reg(r) => {
61             let reg = r.reg_class().unwrap();
62             let reg_lower = reg.to_string().to_lowercase();
63             match op.mutability {
64                 Mutability::Read => {
65                     format!("{ASM}::{reg}::new({r})")
66                 }
67                 Mutability::Write => {
68                     format!("{ASM}::{reg}::new(self.temp_writable_{reg_lower}())")
69                 }
70                 Mutability::ReadWrite => {
71                     format!("self.convert_{reg_lower}_to_assembler_read_write_{reg_lower}({r})")
72                 }
73             }
74         }
75         OperandKind::RegMem(rm) => {
76             let reg = rm.reg_class().unwrap().to_string().to_lowercase();
77             let mut_ = op.mutability.generate_snake_case();
78             let align = if op.align { "_aligned" } else { "" };
79             format!("self.convert_{reg}_mem_to_assembler_{mut_}_{reg}_mem{align}({rm})")
80         }
81         OperandKind::Mem(mem) => format!("self.convert_amode_to_assembler_amode({mem})"),
82     }
83 }
84 
85 /// `fn x64_<inst>(&mut self, <params>) -> Inst<R> { ... }`
86 ///
87 /// # Panics
88 ///
89 /// This function panics if the instruction has no operands.
90 pub fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) {
91     use OperandKind::*;
92 
93     let struct_name = inst.name();
94     let operands = inst.format.operands.iter().cloned().collect::<Vec<_>>();
95     let results = operands
96         .iter()
97         .filter(|o| o.mutability.is_write())
98         .collect::<Vec<_>>();
99     let rust_params = operands
100         .iter()
101         .filter(|o| is_raw_operand_param(o))
102         .map(|o| format!("{}: {}", o.location, rust_param_raw(o)))
103         .chain(if inst.has_trap {
104             Some(format!("trap: &TrapCode"))
105         } else {
106             None
107         })
108         .collect::<Vec<_>>()
109         .join(", ");
110     f.add_block(
111         &format!("fn x64_{struct_name}_raw(&mut self, {rust_params}) -> AssemblerOutputs"),
112         |f| {
113             f.comment("Convert ISLE types to assembler types.");
114             for op in operands.iter() {
115                 let loc = op.location;
116                 let cvt = rust_convert_isle_to_assembler(op);
117                 fmtln!(f, "let {loc} = {cvt};");
118             }
119             let mut args = operands
120                 .iter()
121                 .map(|o| format!("{}.clone()", o.location))
122                 .collect::<Vec<_>>();
123             if inst.has_trap {
124                 args.push(format!("{ASM}::TrapCode(trap.as_raw())"));
125             }
126             let args = args.join(", ");
127             f.empty_line();
128 
129             f.comment("Build the instruction.");
130             fmtln!(
131                 f,
132                 "let inst = {ASM}::inst::{struct_name}::new({args}).into();"
133             );
134             fmtln!(f, "let inst = MInst::External {{ inst }};");
135             f.empty_line();
136 
137             // When an instruction writes to an operand, Cranelift expects a
138             // returned value to use in other instructions: we return this
139             // information in the `AssemblerOutputs` struct defined in ISLE
140             // (below). The general rule here is that memory stores will create
141             // a `SideEffect` whereas for write or read-write registers we will
142             // return some form of `Ret*`.
143             f.comment("Return a type ISLE can work with.");
144             let access_reg = |op: &Operand| match op.mutability {
145                 Mutability::Read => unreachable!(),
146                 Mutability::Write => "to_reg()",
147                 Mutability::ReadWrite => "write.to_reg()",
148             };
149             let ty_var_of_reg = |loc: Location| {
150                 let ty = loc.reg_class().unwrap().to_string();
151                 let var = ty.to_lowercase();
152                 (ty, var)
153             };
154             match results.as_slice() {
155                 [] => fmtln!(f, "SideEffectNoResult::Inst(inst)"),
156                 [op] => match op.location.kind() {
157                     Imm(_) => unreachable!(),
158                     Reg(r) | FixedReg(r) => {
159                         let (ty, var) = ty_var_of_reg(r);
160                         fmtln!(f, "let {var} = {r}.as_ref().{};", access_reg(op));
161                         fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}");
162                     }
163                     Mem(_) => {
164                         fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }}")
165                     }
166                     RegMem(rm) => {
167                         let (ty, var) = ty_var_of_reg(rm);
168                         f.add_block(&format!("match {rm}"), |f| {
169                             f.add_block(&format!("{ASM}::{ty}Mem::{ty}(reg) => "), |f| {
170                                 fmtln!(f, "let {var} = reg.{};", access_reg(op));
171                                 fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }} ");
172                             });
173                             f.add_block(&format!("{ASM}::{ty}Mem::Mem(_) => "), |f| {
174                                 fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }} ");
175                             });
176                         });
177                     }
178                 },
179                 // For now, we assume that if there are two results, they are
180                 // coming from a register-writing instruction like `mul`. The
181                 // `match` below can be expanded as needed.
182                 [op1, op2] => match (op1.location.kind(), op2.location.kind()) {
183                     (FixedReg(loc1) | Reg(loc1), FixedReg(loc2) | Reg(loc2)) => {
184                         fmtln!(f, "let one = {loc1}.as_ref().{}.to_reg();", access_reg(op1));
185                         fmtln!(f, "let two = {loc2}.as_ref().{}.to_reg();", access_reg(op2));
186                         fmtln!(f, "let regs = ValueRegs::two(one, two);");
187                         fmtln!(f, "AssemblerOutputs::RetValueRegs {{ inst, regs }}");
188                     }
189                     _ => unimplemented!("unhandled results: {results:?}"),
190                 },
191                 _ => panic!("instruction has more than one result"),
192             }
193         },
194     );
195 }
196 
197 /// Generate the `isle_assembler_methods!` macro.
198 pub fn generate_rust_macro(f: &mut Formatter, insts: &[Inst]) {
199     fmtln!(f, "#[doc(hidden)]");
200     fmtln!(f, "macro_rules! isle_assembler_methods {{");
201     f.indent(|f| {
202         fmtln!(f, "() => {{");
203         f.indent(|f| {
204             for inst in insts {
205                 generate_macro_inst_fn(f, inst);
206             }
207         });
208         fmtln!(f, "}};");
209     });
210     fmtln!(f, "}}");
211 }
212 
213 /// Returns the type of this operand in ISLE as a part of the ISLE "raw"
214 /// constructors.
215 pub fn isle_param_raw(op: &Operand) -> String {
216     match op.location.kind() {
217         OperandKind::Imm(loc) => {
218             let bits = loc.bits();
219             if op.extension.is_sign_extended() {
220                 format!("i{bits}")
221             } else {
222                 format!("u{bits}")
223             }
224         }
225         OperandKind::Reg(r) | OperandKind::FixedReg(r) => r.reg_class().unwrap().to_string(),
226         OperandKind::Mem(_) => {
227             if op.align {
228                 unimplemented!("no way yet to mark an SyntheticAmode as aligned")
229             } else {
230                 "SyntheticAmode".to_string()
231             }
232         }
233         OperandKind::RegMem(rm) => {
234             let reg = rm.reg_class().unwrap();
235             let aligned = if op.align { "Aligned" } else { "" };
236             format!("{reg}Mem{aligned}")
237         }
238     }
239 }
240 
241 /// Different kinds of ISLE constructors generated for a particular instruction.
242 ///
243 /// One instruction may generate a single constructor or multiple constructors.
244 /// For example an instruction that writes its result to a register will
245 /// generate only a single constructor. An instruction where the destination
246 /// read/write operand is `GprMem` will generate two constructors though, one
247 /// for memory and one for in registers.
248 #[derive(Copy, Clone, Debug)]
249 pub enum IsleConstructor {
250     /// This constructor only produces a side effect, meaning that the
251     /// instruction does not produce results in registers. This may produce
252     /// a result in memory, however.
253     RetMemorySideEffect,
254 
255     /// This constructor produces a `Gpr` value, meaning that the instruction
256     /// will write its result to a single GPR register.
257     RetGpr,
258 
259     /// This is similar to `RetGpr`, but for XMM registers.
260     RetXmm,
261 
262     /// This "special" constructor captures multiple written-to registers (e.g.
263     /// `mul`).
264     RetValueRegs,
265 }
266 
267 impl IsleConstructor {
268     /// Returns the result type, in ISLE, that this constructor generates.
269     pub fn result_ty(&self) -> &'static str {
270         match self {
271             IsleConstructor::RetMemorySideEffect => "SideEffectNoResult",
272             IsleConstructor::RetGpr => "Gpr",
273             IsleConstructor::RetXmm => "Xmm",
274             IsleConstructor::RetValueRegs => "ValueRegs",
275         }
276     }
277 
278     /// Returns the constructor used to convert an `AssemblerOutput` into the
279     /// type returned by [`Self::result_ty`].
280     pub fn conversion_constructor(&self) -> &'static str {
281         match self {
282             IsleConstructor::RetMemorySideEffect => "defer_side_effect",
283             IsleConstructor::RetGpr => "emit_ret_gpr",
284             IsleConstructor::RetXmm => "emit_ret_xmm",
285             IsleConstructor::RetValueRegs => "emit_ret_value_regs",
286         }
287     }
288 
289     /// Returns the suffix used in the ISLE constructor name.
290     pub fn suffix(&self) -> &'static str {
291         match self {
292             IsleConstructor::RetMemorySideEffect => "_mem",
293             IsleConstructor::RetGpr | IsleConstructor::RetXmm | IsleConstructor::RetValueRegs => "",
294         }
295     }
296 
297     /// Returns whether this constructor will include a write-only `RegMem`
298     /// operand as an argument to the constructor.
299     ///
300     /// Memory-based ctors take an `Amode`, but register-based ctors don't take
301     /// the result as an argument and instead manufacture it internally.
302     pub fn includes_write_only_reg_mem(&self) -> bool {
303         match self {
304             IsleConstructor::RetMemorySideEffect => true,
305             IsleConstructor::RetGpr | IsleConstructor::RetXmm | IsleConstructor::RetValueRegs => {
306                 false
307             }
308         }
309     }
310 }
311 
312 /// Returns the parameter type used for the `IsleConstructor` variant
313 /// provided.
314 pub fn isle_param_for_ctor(op: &Operand, ctor: IsleConstructor) -> String {
315     match op.location.kind() {
316         // Writable `RegMem` operands are special here: in one constructor
317         // it's operating on memory so the argument is `Amode` and in the
318         // other constructor it's operating on registers so the argument is
319         // a `Gpr`.
320         OperandKind::RegMem(_) if op.mutability.is_write() => match ctor {
321             IsleConstructor::RetMemorySideEffect => "SyntheticAmode".to_string(),
322             IsleConstructor::RetGpr => "Gpr".to_string(),
323             IsleConstructor::RetXmm => "Xmm".to_string(),
324             IsleConstructor::RetValueRegs => "ValueRegs".to_string(),
325         },
326 
327         // everything else is the same as the "raw" variant
328         _ => isle_param_raw(op),
329     }
330 }
331 
332 /// Returns the ISLE constructors that are going to be used when generating
333 /// this instruction.
334 ///
335 /// Note that one instruction might need multiple constructors, such as one
336 /// for operating on memory and one for operating on registers.
337 pub fn isle_constructors(format: &Format) -> Vec<IsleConstructor> {
338     use Mutability::*;
339     use OperandKind::*;
340 
341     let write_operands = format
342         .operands
343         .iter()
344         .filter(|o| o.mutability.is_write())
345         .collect::<Vec<_>>();
346     match &write_operands[..] {
347         [] => unimplemented!(
348             "if you truly need this (and not a `SideEffect*`), add a `NoReturn` variant to `AssemblerOutputs`"
349         ),
350         [one] => match one.mutability {
351             Read => unreachable!(),
352             ReadWrite | Write => match one.location.kind() {
353                 Imm(_) => unreachable!(),
354                 // One read/write register output? Output the instruction
355                 // and that register.
356                 Reg(r) | FixedReg(r) => match r.reg_class().unwrap() {
357                     RegClass::Xmm => vec![IsleConstructor::RetXmm],
358                     RegClass::Gpr => vec![IsleConstructor::RetGpr],
359                 },
360                 // One read/write memory operand? Output a side effect.
361                 Mem(_) => vec![IsleConstructor::RetMemorySideEffect],
362                 // One read/write reg-mem output? We need constructors for
363                 // both variants.
364                 RegMem(rm) => match rm.reg_class().unwrap() {
365                     RegClass::Xmm => vec![
366                         IsleConstructor::RetXmm,
367                         IsleConstructor::RetMemorySideEffect,
368                     ],
369                     RegClass::Gpr => vec![
370                         IsleConstructor::RetGpr,
371                         IsleConstructor::RetMemorySideEffect,
372                     ],
373                 },
374             },
375         },
376         [one, two] => {
377             // For now, we assume that if there are two results, they are coming
378             // from a register-writing instruction like `mul`. This can be
379             // expanded as needed.
380             assert!(matches!(one.location.kind(), FixedReg(_) | Reg(_)));
381             assert!(matches!(two.location.kind(), FixedReg(_) | Reg(_)));
382             vec![IsleConstructor::RetValueRegs]
383         }
384         other => panic!("unsupported number of write operands {other:?}"),
385     }
386 }
387 
388 /// Generate a "raw" constructor that simply constructs, but does not emit
389 /// the assembly instruction:
390 ///
391 /// ```text
392 /// (decl x64_<inst>_raw (<params>) AssemblerOutputs)
393 /// (extern constructor x64_<inst>_raw x64_<inst>_raw)
394 /// ```
395 ///
396 /// Using the "raw" constructor, we also generate "emitter" constructors
397 /// (see [`IsleConstructor`]). E.g., instructions that write to a register
398 /// will return the register:
399 ///
400 /// ```text
401 /// (decl x64_<inst> (<params>) Gpr)
402 /// (rule (x64_<inst> <params>) (emit_ret_gpr (x64_<inst>_raw <params>)))
403 /// ```
404 ///
405 /// For instructions that write to memory, we also generate an "emitter"
406 /// constructor with the `_mem` suffix:
407 ///
408 /// ```text
409 /// (decl x64_<inst>_mem (<params>) SideEffectNoResult)
410 /// (rule (x64_<inst>_mem <params>) (defer_side_effect (x64_<inst>_raw <params>)))
411 /// ```
412 ///
413 /// # Panics
414 ///
415 /// This function panics if the instruction has no operands.
416 pub fn generate_isle_inst_decls(f: &mut Formatter, inst: &Inst) {
417     let (trap_type, trap_name) = if inst.has_trap {
418         (Some("TrapCode".to_string()), Some("trap".to_string()))
419     } else {
420         (None, None)
421     };
422 
423     // First declare the "raw" constructor which is implemented in Rust
424     // with `generate_isle_macro` above. This is an "extern" constructor
425     // with relatively raw types. This is not intended to be used by
426     // general lowering rules in ISLE.
427     let struct_name = inst.name();
428     let raw_name = format!("x64_{struct_name}_raw");
429     let params = inst
430         .format
431         .operands
432         .iter()
433         .filter(|o| is_raw_operand_param(o))
434         .collect::<Vec<_>>();
435     let raw_param_tys = params
436         .iter()
437         .map(|o| isle_param_raw(o))
438         .chain(trap_type.clone())
439         .collect::<Vec<_>>()
440         .join(" ");
441     fmtln!(f, "(decl {raw_name} ({raw_param_tys}) AssemblerOutputs)");
442     fmtln!(f, "(extern constructor {raw_name} {raw_name})");
443 
444     // Next, for each "emitter" ISLE constructor being generated, synthesize
445     // a pure-ISLE constructor which delegates appropriately to the `*_raw`
446     // constructor above.
447     //
448     // The main purpose of these constructors is to have faithful type
449     // signatures for the SSA nature of VCode/ISLE, effectively translating
450     // x64's type system to ISLE/VCode's type system.
451     //
452     // Note that the `params` from above are partitioned into explicit/implicit
453     // parameters based on the `ctor` we're generating here. That means, for
454     // example, that a write-only `RegMem` will have one ctor which produces a
455     // register that takes no argument, but one ctors will take an `Amode` which
456     // is the address to write to.
457     for ctor in isle_constructors(&inst.format) {
458         let suffix = ctor.suffix();
459         let rule_name = format!("x64_{struct_name}{suffix}");
460         let result_ty = ctor.result_ty();
461         let mut explicit_params = Vec::new();
462         let mut implicit_params = Vec::new();
463         for param in params.iter() {
464             if param.mutability.is_read() || ctor.includes_write_only_reg_mem() {
465                 explicit_params.push(param);
466             } else {
467                 implicit_params.push(param);
468             }
469         }
470         assert!(implicit_params.len() <= 1);
471         let param_tys = explicit_params
472             .iter()
473             .map(|o| isle_param_for_ctor(o, ctor))
474             .chain(trap_type.clone())
475             .collect::<Vec<_>>()
476             .join(" ");
477         let param_names = explicit_params
478             .iter()
479             .map(|o| o.location.to_string())
480             .chain(trap_name.clone())
481             .collect::<Vec<_>>()
482             .join(" ");
483         let convert = ctor.conversion_constructor();
484 
485         // Generate implicit parameters to the `*_raw` constructor. Currently
486         // this is only destination gpr/xmm temps if the result of this entire
487         // constructor is a gpr/xmm register.
488         let implicit_params = implicit_params
489             .iter()
490             .map(|o| {
491                 assert!(matches!(o.location.kind(), OperandKind::RegMem(_)));
492                 match ctor {
493                     IsleConstructor::RetMemorySideEffect => unreachable!(),
494                     IsleConstructor::RetGpr => "(temp_writable_gpr)",
495                     IsleConstructor::RetXmm => "(temp_writable_xmm)",
496                     IsleConstructor::RetValueRegs => todo!(),
497                 }
498             })
499             .collect::<Vec<_>>()
500             .join(" ");
501 
502         fmtln!(f, "(decl {rule_name} ({param_tys}) {result_ty})");
503         fmtln!(
504             f,
505             "(rule ({rule_name} {param_names}) ({convert} ({raw_name} {implicit_params} {param_names})))"
506         );
507     }
508 }
509 
510 /// Generate the ISLE definitions that match the `isle_assembler_methods!` macro
511 /// above.
512 pub fn generate_isle(f: &mut Formatter, insts: &[Inst]) {
513     fmtln!(f, "(type AssemblerOutputs (enum");
514     fmtln!(f, "    ;; Used for instructions that have ISLE");
515     fmtln!(f, "    ;; `SideEffect`s (memory stores, traps,");
516     fmtln!(f, "    ;; etc.) and do not return a `Value`.");
517     fmtln!(f, "    (SideEffect (inst MInst))");
518     fmtln!(f, "    ;; Used for instructions that return a");
519     fmtln!(f, "    ;; GPR (including `GprMem` variants with");
520     fmtln!(f, "    ;; a GPR as the first argument).");
521     fmtln!(f, "    (RetGpr (inst MInst) (gpr Gpr))");
522     fmtln!(f, "    ;; Used for instructions that return an");
523     fmtln!(f, "    ;; XMM register.");
524     fmtln!(f, "    (RetXmm (inst MInst) (xmm Xmm))");
525     fmtln!(f, "    ;; Used for multi-return instructions.");
526     fmtln!(f, "    (RetValueRegs (inst MInst) (regs ValueRegs))");
527     fmtln!(
528         f,
529         "    ;; https://github.com/bytecodealliance/wasmtime/pull/10276"
530     );
531     fmtln!(f, "))");
532     f.empty_line();
533 
534     fmtln!(f, ";; Directly emit instructions that return a GPR.");
535     fmtln!(f, "(decl emit_ret_gpr (AssemblerOutputs) Gpr)");
536     fmtln!(f, "(rule (emit_ret_gpr (AssemblerOutputs.RetGpr inst gpr))");
537     fmtln!(f, "    (let ((_ Unit (emit inst))) gpr))");
538     f.empty_line();
539 
540     fmtln!(f, ";; Directly emit instructions that return an");
541     fmtln!(f, ";; XMM register.");
542     fmtln!(f, "(decl emit_ret_xmm (AssemblerOutputs) Xmm)");
543     fmtln!(f, "(rule (emit_ret_xmm (AssemblerOutputs.RetXmm inst xmm))");
544     fmtln!(f, "    (let ((_ Unit (emit inst))) xmm))");
545     f.empty_line();
546 
547     fmtln!(f, ";; Directly emit instructions that return multiple");
548     fmtln!(f, ";; registers (e.g. `mul`).");
549     fmtln!(f, "(decl emit_ret_value_regs (AssemblerOutputs) ValueRegs)");
550     fmtln!(
551         f,
552         "(rule (emit_ret_value_regs (AssemblerOutputs.RetValueRegs inst regs))"
553     );
554     fmtln!(f, "    (let ((_ Unit (emit inst))) regs))");
555     f.empty_line();
556 
557     fmtln!(f, ";; Pass along the side-effecting instruction");
558     fmtln!(f, ";; for later emission.");
559     fmtln!(
560         f,
561         "(decl defer_side_effect (AssemblerOutputs) SideEffectNoResult)"
562     );
563     fmtln!(
564         f,
565         "(rule (defer_side_effect (AssemblerOutputs.SideEffect inst))"
566     );
567     fmtln!(f, "    (SideEffectNoResult.Inst inst))");
568     f.empty_line();
569 
570     for inst in insts {
571         generate_isle_inst_decls(f, inst);
572         f.empty_line();
573     }
574 }
575 
576 /// Returns whether `o` is included in the `*_raw` constructor generated in
577 /// ISLE/Rust.
578 ///
579 /// This notably includes all operands that are read as those are the
580 /// data-dependencies of an instruction. This additionally includes, though,
581 /// write-only `RegMem` operands. In this situation the `RegMem` operand is
582 /// dynamically a `RegMem::Reg`, a temp register synthesized in ISLE, or a
583 /// `RegMem::Mem`, an operand from the constructor of the original entrypoint
584 /// itself.
585 fn is_raw_operand_param(o: &Operand) -> bool {
586     o.mutability.is_read()
587         || matches!(
588             o.location.kind(),
589             OperandKind::RegMem(_) | OperandKind::Mem(_)
590         )
591 }
592