Skip to main content

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