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