cranelift_codegen_meta/
gen_asm.rs

1//! Generate the Cranelift-specific integration of the x64 assembler.
2
3use cranelift_assembler_x64_meta::dsl::{
4    Feature, Format, Inst, Location, Mutability, Operand, OperandKind, RegClass,
5};
6use cranelift_srcgen::{Formatter, fmtln};
7
8/// This factors out use of the assembler crate name.
9const ASM: &str = "cranelift_assembler_x64";
10
11fn 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.
22fn 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.
47fn 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.
100fn 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.
228pub 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.
247fn 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)]
281enum 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    /// This constructor produces no results, but the flags register is written,
302    /// so a `ProducesFlags` value is returned with a side effect.
303    ProducesFlagsSideEffect,
304
305    /// This instructions reads EFLAGS, and returns a single gpr, so this
306    /// creates `ConsumesFlags.ConsumesFlagsReturnsReg`.
307    ConsumesFlagsReturnsGpr,
308}
309
310impl IsleConstructor {
311    /// Returns the result type, in ISLE, that this constructor generates.
312    fn result_ty(&self) -> &'static str {
313        match self {
314            IsleConstructor::RetGpr => "Gpr",
315            IsleConstructor::RetXmm => "Xmm",
316            IsleConstructor::RetValueRegs => "ValueRegs",
317            IsleConstructor::NoReturnSideEffect | IsleConstructor::RetMemorySideEffect => {
318                "SideEffectNoResult"
319            }
320            IsleConstructor::ProducesFlagsSideEffect => "ProducesFlags",
321            IsleConstructor::ConsumesFlagsReturnsGpr => "ConsumesFlags",
322        }
323    }
324
325    /// Returns the constructor used to convert an `AssemblerOutput` into the
326    /// type returned by [`Self::result_ty`].
327    fn conversion_constructor(&self) -> &'static str {
328        match self {
329            IsleConstructor::NoReturnSideEffect | IsleConstructor::RetMemorySideEffect => {
330                "defer_side_effect"
331            }
332            IsleConstructor::RetGpr => "emit_ret_gpr",
333            IsleConstructor::RetXmm => "emit_ret_xmm",
334            IsleConstructor::RetValueRegs => "emit_ret_value_regs",
335            IsleConstructor::ProducesFlagsSideEffect => "asm_produce_flags_side_effect",
336            IsleConstructor::ConsumesFlagsReturnsGpr => "asm_consumes_flags_returns_gpr",
337        }
338    }
339
340    /// Returns the suffix used in the ISLE constructor name.
341    fn suffix(&self) -> &'static str {
342        match self {
343            IsleConstructor::RetMemorySideEffect => "_mem",
344            IsleConstructor::RetGpr
345            | IsleConstructor::RetXmm
346            | IsleConstructor::RetValueRegs
347            | IsleConstructor::NoReturnSideEffect
348            | IsleConstructor::ProducesFlagsSideEffect
349            | IsleConstructor::ConsumesFlagsReturnsGpr => "",
350        }
351    }
352
353    /// Returns whether this constructor will include a write-only `RegMem`
354    /// operand as an argument to the constructor.
355    ///
356    /// Memory-based ctors take an `Amode`, but register-based ctors don't take
357    /// the result as an argument and instead manufacture it internally.
358    fn includes_write_only_reg_mem(&self) -> bool {
359        match self {
360            IsleConstructor::RetMemorySideEffect => true,
361            IsleConstructor::RetGpr
362            | IsleConstructor::RetXmm
363            | IsleConstructor::RetValueRegs
364            | IsleConstructor::NoReturnSideEffect
365            | IsleConstructor::ProducesFlagsSideEffect
366            | IsleConstructor::ConsumesFlagsReturnsGpr => false,
367        }
368    }
369}
370
371/// Returns the parameter type used for the `IsleConstructor` variant
372/// provided.
373fn isle_param_for_ctor(op: &Operand, ctor: IsleConstructor) -> String {
374    match op.location.kind() {
375        // Writable `RegMem` operands are special here: in one constructor
376        // it's operating on memory so the argument is `Amode` and in the
377        // other constructor it's operating on registers so the argument is
378        // a `Gpr`.
379        OperandKind::RegMem(_) if op.mutability.is_write() => match ctor {
380            IsleConstructor::RetMemorySideEffect => "SyntheticAmode".to_string(),
381            IsleConstructor::NoReturnSideEffect => "".to_string(),
382            IsleConstructor::RetGpr | IsleConstructor::ConsumesFlagsReturnsGpr => "Gpr".to_string(),
383            IsleConstructor::RetXmm => "Xmm".to_string(),
384            IsleConstructor::RetValueRegs => "ValueRegs".to_string(),
385            IsleConstructor::ProducesFlagsSideEffect => todo!(),
386        },
387
388        // everything else is the same as the "raw" variant
389        _ => isle_param_raw(op),
390    }
391}
392
393/// Returns the ISLE constructors that are going to be used when generating
394/// this instruction.
395///
396/// Note that one instruction might need multiple constructors, such as one
397/// for operating on memory and one for operating on registers.
398fn isle_constructors(format: &Format) -> Vec<IsleConstructor> {
399    use Mutability::*;
400    use OperandKind::*;
401
402    let write_operands = format
403        .operands
404        .iter()
405        .filter(|o| o.mutability.is_write())
406        .collect::<Vec<_>>();
407    match &write_operands[..] {
408        [] => {
409            if format.eflags.is_write() {
410                vec![IsleConstructor::ProducesFlagsSideEffect]
411            } else {
412                vec![IsleConstructor::NoReturnSideEffect]
413            }
414        }
415        [one] => match one.mutability {
416            Read => unreachable!(),
417            ReadWrite | Write => match one.location.kind() {
418                Imm(_) => unreachable!(),
419                // One read/write register output? Output the instruction
420                // and that register.
421                Reg(r) | FixedReg(r) => match r.reg_class().unwrap() {
422                    RegClass::Xmm => {
423                        assert!(!format.eflags.is_read());
424                        vec![IsleConstructor::RetXmm]
425                    }
426                    RegClass::Gpr => {
427                        if format.eflags.is_read() {
428                            vec![IsleConstructor::ConsumesFlagsReturnsGpr]
429                        } else {
430                            vec![IsleConstructor::RetGpr]
431                        }
432                    }
433                },
434                // One read/write memory operand? Output a side effect.
435                Mem(_) => {
436                    assert!(!format.eflags.is_read());
437                    vec![IsleConstructor::RetMemorySideEffect]
438                }
439                // One read/write reg-mem output? We need constructors for
440                // both variants.
441                RegMem(rm) => match rm.reg_class().unwrap() {
442                    RegClass::Xmm => {
443                        assert!(!format.eflags.is_read());
444                        vec![
445                            IsleConstructor::RetXmm,
446                            IsleConstructor::RetMemorySideEffect,
447                        ]
448                    }
449                    RegClass::Gpr => {
450                        if format.eflags.is_read() {
451                            // FIXME: should expand this to include "consumes
452                            // flags plus side effect" to model the
453                            // memory-writing variant too. For example this
454                            // means there's no memory-writing variant of
455                            // `setcc` instructions generated.
456                            vec![IsleConstructor::ConsumesFlagsReturnsGpr]
457                        } else {
458                            vec![
459                                IsleConstructor::RetGpr,
460                                IsleConstructor::RetMemorySideEffect,
461                            ]
462                        }
463                    }
464                },
465            },
466        },
467        [one, two] => {
468            assert!(!format.eflags.is_read());
469            match (one.location.kind(), two.location.kind()) {
470                (FixedReg(_) | Reg(_), FixedReg(_) | Reg(_)) => {
471                    vec![IsleConstructor::RetValueRegs]
472                }
473                (Reg(r), Mem(_)) | (Mem(_) | RegMem(_), Reg(r) | FixedReg(r)) => {
474                    assert!(matches!(r.reg_class().unwrap(), RegClass::Gpr));
475                    vec![IsleConstructor::RetGpr]
476                }
477                other => panic!("unsupported number of write operands {other:?}"),
478            }
479        }
480        [one, two, three] => {
481            assert!(!format.eflags.is_read());
482            match (
483                one.location.kind(),
484                two.location.kind(),
485                three.location.kind(),
486            ) {
487                (FixedReg(_), FixedReg(_), Mem(_)) => {
488                    vec![IsleConstructor::RetValueRegs]
489                }
490                other => panic!("unsupported number of write operands {other:?}"),
491            }
492        }
493
494        other => panic!("unsupported number of write operands {other:?}"),
495    }
496}
497
498/// Generate a "raw" constructor that simply constructs, but does not emit
499/// the assembly instruction:
500///
501/// ```text
502/// (decl x64_<inst>_raw (<params>) AssemblerOutputs)
503/// (extern constructor x64_<inst>_raw x64_<inst>_raw)
504/// ```
505///
506/// Using the "raw" constructor, we also generate "emitter" constructors
507/// (see [`IsleConstructor`]). E.g., instructions that write to a register
508/// will return the register:
509///
510/// ```text
511/// (decl x64_<inst> (<params>) Gpr)
512/// (rule (x64_<inst> <params>) (emit_ret_gpr (x64_<inst>_raw <params>)))
513/// ```
514///
515/// For instructions that write to memory, we also generate an "emitter"
516/// constructor with the `_mem` suffix:
517///
518/// ```text
519/// (decl x64_<inst>_mem (<params>) SideEffectNoResult)
520/// (rule (x64_<inst>_mem <params>) (defer_side_effect (x64_<inst>_raw <params>)))
521/// ```
522///
523/// # Panics
524///
525/// This function panics if the instruction has no operands.
526fn generate_isle_inst_decls(f: &mut Formatter, inst: &Inst) {
527    let (trap_type, trap_name) = if inst.has_trap {
528        (Some("TrapCode".to_string()), Some("trap".to_string()))
529    } else {
530        (None, None)
531    };
532
533    // First declare the "raw" constructor which is implemented in Rust
534    // with `generate_isle_macro` above. This is an "extern" constructor
535    // with relatively raw types. This is not intended to be used by
536    // general lowering rules in ISLE.
537    let struct_name = inst.name();
538    let raw_name = format!("x64_{struct_name}_raw");
539    let params = inst
540        .format
541        .operands
542        .iter()
543        .filter(|o| is_raw_operand_param(o))
544        .collect::<Vec<_>>();
545    let raw_param_tys = params
546        .iter()
547        .map(|o| isle_param_raw(o))
548        .chain(trap_type.clone())
549        .collect::<Vec<_>>()
550        .join(" ");
551    fmtln!(f, "(decl {raw_name} ({raw_param_tys}) AssemblerOutputs)");
552    fmtln!(f, "(extern constructor {raw_name} {raw_name})");
553
554    // Next, for each "emitter" ISLE constructor being generated, synthesize
555    // a pure-ISLE constructor which delegates appropriately to the `*_raw`
556    // constructor above.
557    //
558    // The main purpose of these constructors is to have faithful type
559    // signatures for the SSA nature of VCode/ISLE, effectively translating
560    // x64's type system to ISLE/VCode's type system.
561    //
562    // Note that the `params` from above are partitioned into explicit/implicit
563    // parameters based on the `ctor` we're generating here. That means, for
564    // example, that a write-only `RegMem` will have one ctor which produces a
565    // register that takes no argument, but one ctors will take an `Amode` which
566    // is the address to write to.
567    for ctor in isle_constructors(&inst.format) {
568        let suffix = ctor.suffix();
569        let rule_name = format!("x64_{struct_name}{suffix}");
570        let result_ty = ctor.result_ty();
571        let mut explicit_params = Vec::new();
572        let mut implicit_params = Vec::new();
573        for param in params.iter() {
574            if param.mutability.is_read() || ctor.includes_write_only_reg_mem() {
575                explicit_params.push(param);
576            } else {
577                implicit_params.push(param);
578            }
579        }
580        assert!(implicit_params.len() <= 1);
581        let param_tys = explicit_params
582            .iter()
583            .map(|o| isle_param_for_ctor(o, ctor))
584            .chain(trap_type.clone())
585            .collect::<Vec<_>>()
586            .join(" ");
587        let param_names = explicit_params
588            .iter()
589            .map(|o| o.location.to_string())
590            .chain(trap_name.clone())
591            .collect::<Vec<_>>()
592            .join(" ");
593        let convert = ctor.conversion_constructor();
594
595        // Generate implicit parameters to the `*_raw` constructor. Currently
596        // this is only destination gpr/xmm temps if the result of this entire
597        // constructor is a gpr/xmm register.
598        let implicit_params = implicit_params
599            .iter()
600            .map(|o| {
601                assert!(matches!(o.location.kind(), OperandKind::RegMem(_)));
602                match ctor {
603                    IsleConstructor::RetMemorySideEffect | IsleConstructor::NoReturnSideEffect => {
604                        unreachable!()
605                    }
606                    IsleConstructor::RetGpr | IsleConstructor::ConsumesFlagsReturnsGpr => {
607                        "(temp_writable_gpr)"
608                    }
609                    IsleConstructor::RetXmm => "(temp_writable_xmm)",
610                    IsleConstructor::RetValueRegs | IsleConstructor::ProducesFlagsSideEffect => {
611                        todo!()
612                    }
613                }
614            })
615            .collect::<Vec<_>>()
616            .join(" ");
617
618        fmtln!(f, "(decl {rule_name} ({param_tys}) {result_ty})");
619        fmtln!(
620            f,
621            "(rule ({rule_name} {param_names}) ({convert} ({raw_name} {implicit_params} {param_names})))"
622        );
623
624        if let Some(alternate) = &inst.alternate {
625            // We currently plan to use alternate instructions for SSE/AVX
626            // pairs, so we expect the one of the registers to be an XMM
627            // register. In the future we could relax this, but would need to
628            // handle more cases below.
629            assert!(
630                inst.format
631                    .operands
632                    .iter()
633                    .any(|o| matches!(o.location.reg_class(), Some(RegClass::Xmm)))
634            );
635            let param_tys = if alternate.feature == Feature::avx {
636                param_tys.replace("Aligned", "")
637            } else {
638                param_tys
639            };
640            let alt_feature = alternate.feature.to_string();
641            let alt_name = &alternate.name;
642            let rule_name_or_feat = format!("{rule_name}_or_{alt_feature}");
643            fmtln!(f, "(decl {rule_name_or_feat} ({param_tys}) {result_ty})");
644            fmtln!(f, "(rule 1 ({rule_name_or_feat} {param_names})");
645            f.indent(|f| {
646                fmtln!(f, "(if-let true (use_{alt_feature}))");
647                fmtln!(f, "(x64_{alt_name}{suffix} {param_names}))");
648            });
649            fmtln!(
650                f,
651                "(rule 0 ({rule_name_or_feat} {param_names}) ({rule_name} {param_names}))"
652            );
653        }
654    }
655}
656
657/// Generate the ISLE definitions that match the `isle_assembler_methods!` macro
658/// above.
659pub fn generate_isle(f: &mut Formatter, insts: &[Inst]) {
660    fmtln!(f, "(type AssemblerOutputs (enum");
661    fmtln!(f, "    ;; Used for instructions that have ISLE");
662    fmtln!(f, "    ;; `SideEffect`s (memory stores, traps,");
663    fmtln!(f, "    ;; etc.) and do not return a `Value`.");
664    fmtln!(f, "    (SideEffect (inst MInst))");
665    fmtln!(f, "    ;; Used for instructions that return a");
666    fmtln!(f, "    ;; GPR (including `GprMem` variants with");
667    fmtln!(f, "    ;; a GPR as the first argument).");
668    fmtln!(f, "    (RetGpr (inst MInst) (gpr Gpr))");
669    fmtln!(f, "    ;; Used for instructions that return an");
670    fmtln!(f, "    ;; XMM register.");
671    fmtln!(f, "    (RetXmm (inst MInst) (xmm Xmm))");
672    fmtln!(f, "    ;; Used for multi-return instructions.");
673    fmtln!(f, "    (RetValueRegs (inst MInst) (regs ValueRegs))");
674    fmtln!(
675        f,
676        "    ;; https://github.com/bytecodealliance/wasmtime/pull/10276"
677    );
678    fmtln!(f, "))");
679    f.empty_line();
680
681    fmtln!(f, ";; Directly emit instructions that return a GPR.");
682    fmtln!(f, "(decl emit_ret_gpr (AssemblerOutputs) Gpr)");
683    fmtln!(f, "(rule (emit_ret_gpr (AssemblerOutputs.RetGpr inst gpr))");
684    fmtln!(f, "    (let ((_ Unit (emit inst))) gpr))");
685    f.empty_line();
686
687    fmtln!(f, ";; Directly emit instructions that return an");
688    fmtln!(f, ";; XMM register.");
689    fmtln!(f, "(decl emit_ret_xmm (AssemblerOutputs) Xmm)");
690    fmtln!(f, "(rule (emit_ret_xmm (AssemblerOutputs.RetXmm inst xmm))");
691    fmtln!(f, "    (let ((_ Unit (emit inst))) xmm))");
692    f.empty_line();
693
694    fmtln!(f, ";; Directly emit instructions that return multiple");
695    fmtln!(f, ";; registers (e.g. `mul`).");
696    fmtln!(f, "(decl emit_ret_value_regs (AssemblerOutputs) ValueRegs)");
697    fmtln!(
698        f,
699        "(rule (emit_ret_value_regs (AssemblerOutputs.RetValueRegs inst regs))"
700    );
701    fmtln!(f, "    (let ((_ Unit (emit inst))) regs))");
702    f.empty_line();
703
704    fmtln!(f, ";; Pass along the side-effecting instruction");
705    fmtln!(f, ";; for later emission.");
706    fmtln!(
707        f,
708        "(decl defer_side_effect (AssemblerOutputs) SideEffectNoResult)"
709    );
710    fmtln!(
711        f,
712        "(rule (defer_side_effect (AssemblerOutputs.SideEffect inst))"
713    );
714    fmtln!(f, "    (SideEffectNoResult.Inst inst))");
715    f.empty_line();
716
717    for inst in insts {
718        if include_inst(inst) {
719            generate_isle_inst_decls(f, inst);
720            f.empty_line();
721        }
722    }
723}
724
725/// Returns whether `o` is included in the `*_raw` constructor generated in
726/// ISLE/Rust.
727///
728/// This notably includes all operands that are read as those are the
729/// data-dependencies of an instruction. This additionally includes, though,
730/// write-only `RegMem` operands. In this situation the `RegMem` operand is
731/// dynamically a `RegMem::Reg`, a temp register synthesized in ISLE, or a
732/// `RegMem::Mem`, an operand from the constructor of the original entrypoint
733/// itself.
734fn is_raw_operand_param(o: &Operand) -> bool {
735    o.mutability.is_read()
736        || matches!(
737            o.location.kind(),
738            OperandKind::RegMem(_) | OperandKind::Mem(_)
739        )
740}