cranelift_codegen_meta/
gen_asm.rs

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