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