cranelift_codegen_meta/
gen_isle.rs

1use crate::cdsl::formats::InstructionFormat;
2use crate::cdsl::instructions::AllInstructions;
3use crate::error;
4use cranelift_srcgen::{fmtln, Formatter, Language};
5use std::rc::Rc;
6
7/// Which ISLE target are we generating code for?
8#[derive(Clone, Copy, PartialEq, Eq)]
9enum IsleTarget {
10    /// Generating code for instruction selection and lowering.
11    Lower,
12    /// Generating code for CLIF to CLIF optimizations.
13    Opt,
14}
15
16fn gen_common_isle(
17    formats: &[Rc<InstructionFormat>],
18    instructions: &AllInstructions,
19    fmt: &mut Formatter,
20    isle_target: IsleTarget,
21) {
22    use std::collections::{BTreeMap, BTreeSet};
23    use std::fmt::Write;
24
25    use crate::cdsl::formats::FormatField;
26
27    fmt.multi_line(
28        r#"
29;; GENERATED BY `gen_isle`. DO NOT EDIT!!!
30;;
31;; This ISLE file defines all the external type declarations for Cranelift's
32;; data structures that ISLE will process, such as `InstructionData` and
33;; `Opcode`.
34        "#,
35    );
36    fmt.empty_line();
37
38    // Collect and deduplicate the immediate types from the instruction fields.
39    let rust_name = |f: &FormatField| f.kind.rust_type.rsplit("::").next().unwrap();
40    let fields = |f: &FormatField| f.kind.fields.clone();
41    let immediate_types: BTreeMap<_, _> = formats
42        .iter()
43        .flat_map(|f| {
44            f.imm_fields
45                .iter()
46                .map(|i| (rust_name(i), fields(i)))
47                .collect::<Vec<_>>()
48        })
49        .collect();
50
51    // Separate the `enum` immediates (e.g., `FloatCC`) from other kinds of
52    // immediates.
53    let (enums, others): (BTreeMap<_, _>, BTreeMap<_, _>) = immediate_types
54        .iter()
55        .partition(|(_, field)| field.enum_values().is_some());
56
57    // Generate all the extern type declarations we need for the non-`enum`
58    // immediates.
59    fmt.line(";;;; Extern type declarations for immediates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
60    fmt.empty_line();
61    for ty in others.keys() {
62        fmtln!(fmt, "(type {} (primitive {}))", ty, ty);
63    }
64    fmt.empty_line();
65
66    // Generate the `enum` immediates, expanding all of the available variants
67    // into ISLE.
68    for (name, field) in enums {
69        let field = field.enum_values().expect("only enums considered here");
70        let variants = field.values().cloned().collect();
71        gen_isle_enum(name, variants, fmt)
72    }
73
74    // Generate all of the value arrays we need for `InstructionData` as well as
75    // the constructors and extractors for them.
76    fmt.line(";;;; Value Arrays ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
77    fmt.empty_line();
78    let value_array_arities: BTreeSet<_> = formats
79        .iter()
80        .filter(|f| f.typevar_operand.is_some() && !f.has_value_list && f.num_value_operands != 1)
81        .map(|f| f.num_value_operands)
82        .collect();
83    for n in value_array_arities {
84        fmtln!(fmt, ";; ISLE representation of `[Value; {}]`.", n);
85        fmtln!(fmt, "(type ValueArray{} extern (enum))", n);
86        fmt.empty_line();
87
88        fmtln!(
89            fmt,
90            "(decl value_array_{} ({}) ValueArray{})",
91            n,
92            (0..n).map(|_| "Value").collect::<Vec<_>>().join(" "),
93            n
94        );
95        fmtln!(
96            fmt,
97            "(extern constructor value_array_{} pack_value_array_{})",
98            n,
99            n
100        );
101        fmtln!(
102            fmt,
103            "(extern extractor infallible value_array_{} unpack_value_array_{})",
104            n,
105            n
106        );
107        fmt.empty_line();
108    }
109
110    // Generate all of the block arrays we need for `InstructionData` as well as
111    // the constructors and extractors for them.
112    fmt.line(";;;; Block Arrays ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
113    fmt.empty_line();
114    let block_array_arities: BTreeSet<_> = formats
115        .iter()
116        .filter(|f| f.num_block_operands > 1)
117        .map(|f| f.num_block_operands)
118        .collect();
119    for n in block_array_arities {
120        fmtln!(fmt, ";; ISLE representation of `[BlockCall; {}]`.", n);
121        fmtln!(fmt, "(type BlockArray{} extern (enum))", n);
122        fmt.empty_line();
123
124        fmtln!(
125            fmt,
126            "(decl block_array_{0} ({1}) BlockArray{0})",
127            n,
128            (0..n).map(|_| "BlockCall").collect::<Vec<_>>().join(" ")
129        );
130
131        fmtln!(
132            fmt,
133            "(extern constructor block_array_{0} pack_block_array_{0})",
134            n
135        );
136
137        fmtln!(
138            fmt,
139            "(extern extractor infallible block_array_{0} unpack_block_array_{0})",
140            n
141        );
142        fmt.empty_line();
143    }
144
145    // Generate the extern type declaration for `Opcode`.
146    fmt.line(";;;; `Opcode` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
147    fmt.empty_line();
148    fmt.line("(type Opcode extern");
149    fmt.indent(|fmt| {
150        fmt.line("(enum");
151        fmt.indent(|fmt| {
152            for inst in instructions {
153                fmtln!(fmt, "{}", inst.camel_name);
154            }
155        });
156        fmt.line(")");
157    });
158    fmt.line(")");
159    fmt.empty_line();
160
161    // Generate the extern type declaration for `InstructionData`.
162    fmtln!(
163        fmt,
164        ";;;; `InstructionData` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
165    );
166    fmt.empty_line();
167    fmtln!(fmt, "(type InstructionData extern");
168    fmt.indent(|fmt| {
169        fmt.line("(enum");
170        fmt.indent(|fmt| {
171            for format in formats {
172                let mut s = format!("({} (opcode Opcode)", format.name);
173                if format.has_value_list {
174                    s.push_str(" (args ValueList)");
175                } else if format.num_value_operands == 1 {
176                    s.push_str(" (arg Value)");
177                } else if format.num_value_operands > 1 {
178                    write!(&mut s, " (args ValueArray{})", format.num_value_operands).unwrap();
179                }
180
181                match format.num_block_operands {
182                    0 => (),
183                    1 => write!(&mut s, " (destination BlockCall)").unwrap(),
184                    n => write!(&mut s, " (blocks BlockArray{n})").unwrap(),
185                }
186
187                for field in &format.imm_fields {
188                    write!(
189                        &mut s,
190                        " ({} {})",
191                        field.member,
192                        field.kind.rust_type.rsplit("::").next().unwrap()
193                    )
194                    .unwrap();
195                }
196                s.push(')');
197                fmt.line(&s);
198            }
199        });
200        fmt.line(")");
201    });
202    fmt.line(")");
203    fmt.empty_line();
204
205    // Generate the helper extractors for each opcode's full instruction.
206    fmtln!(
207        fmt,
208        ";;;; Extracting Opcode, Operands, and Immediates from `InstructionData` ;;;;;;;;",
209    );
210    fmt.empty_line();
211    for inst in instructions {
212        let results_len = inst.value_results.len();
213        let is_var_args = inst.format.has_value_list;
214        let has_side_effects = inst.can_trap || inst.other_side_effects;
215
216        let (ret_ty, ty_in_decl, make_inst_ctor, inst_data_etor) =
217            match (isle_target, is_var_args, results_len, has_side_effects) {
218                // The mid-end does not deal with instructions that have var-args right now.
219                (IsleTarget::Opt, true, _, _) => continue,
220
221                (IsleTarget::Opt, _, 1, false) => ("Value", true, "make_inst", "inst_data_value"),
222                (IsleTarget::Opt, _, _, _) => ("Inst", false, "make_skeleton_inst", "inst_data"),
223                (IsleTarget::Lower, _, _, _) => ("Inst", false, "make_inst", "inst_data_value"),
224            };
225
226        fmtln!(
227            fmt,
228            "(decl {} ({}{}) {})",
229            inst.name,
230            if ty_in_decl { "Type " } else { "" },
231            inst.operands_in
232                .iter()
233                .map(|o| {
234                    let ty = o.kind.rust_type;
235                    if ty == "&[Value]" {
236                        "ValueSlice"
237                    } else {
238                        ty.rsplit("::").next().unwrap()
239                    }
240                })
241                .collect::<Vec<_>>()
242                .join(" "),
243            ret_ty
244        );
245        fmtln!(fmt, "(extractor");
246        fmt.indent(|fmt| {
247            fmtln!(
248                fmt,
249                "({} {}{})",
250                inst.name,
251                if ty_in_decl { "ty " } else { "" },
252                inst.operands_in
253                    .iter()
254                    .map(|o| { o.name })
255                    .collect::<Vec<_>>()
256                    .join(" ")
257            );
258
259            let mut s = format!(
260                "({inst_data_etor} {}(InstructionData.{} (Opcode.{})",
261                if ty_in_decl { "ty " } else { "" },
262                inst.format.name,
263                inst.camel_name
264            );
265
266            // Value and varargs operands.
267            if inst.format.has_value_list {
268                // The instruction format uses a value list, but the
269                // instruction itself might have not only a `&[Value]`
270                // varargs operand, but also one or more `Value` operands as
271                // well. If this is the case, then we need to read them off
272                // the front of the `ValueList`.
273                let values: Vec<_> = inst
274                    .operands_in
275                    .iter()
276                    .filter(|o| o.is_value())
277                    .map(|o| o.name)
278                    .collect();
279                let varargs = inst
280                    .operands_in
281                    .iter()
282                    .find(|o| o.is_varargs())
283                    .unwrap()
284                    .name;
285                if values.is_empty() {
286                    write!(&mut s, " (value_list_slice {varargs})").unwrap();
287                } else {
288                    write!(
289                        &mut s,
290                        " (unwrap_head_value_list_{} {} {})",
291                        values.len(),
292                        values.join(" "),
293                        varargs
294                    )
295                    .unwrap();
296                }
297            } else if inst.format.num_value_operands == 1 {
298                write!(
299                    &mut s,
300                    " {}",
301                    inst.operands_in.iter().find(|o| o.is_value()).unwrap().name
302                )
303                .unwrap();
304            } else if inst.format.num_value_operands > 1 {
305                let values = inst
306                    .operands_in
307                    .iter()
308                    .filter(|o| o.is_value())
309                    .map(|o| o.name)
310                    .collect::<Vec<_>>();
311                assert_eq!(values.len(), inst.format.num_value_operands);
312                let values = values.join(" ");
313                write!(
314                    &mut s,
315                    " (value_array_{} {})",
316                    inst.format.num_value_operands, values,
317                )
318                .unwrap();
319            }
320
321            // Immediates.
322            let imm_operands: Vec<_> = inst
323                .operands_in
324                .iter()
325                .filter(|o| !o.is_value() && !o.is_varargs() && !o.kind.is_block())
326                .collect();
327            assert_eq!(imm_operands.len(), inst.format.imm_fields.len(),);
328            for op in imm_operands {
329                write!(&mut s, " {}", op.name).unwrap();
330            }
331
332            // Blocks.
333            let block_operands: Vec<_> = inst
334                .operands_in
335                .iter()
336                .filter(|o| o.kind.is_block())
337                .collect();
338            assert_eq!(block_operands.len(), inst.format.num_block_operands);
339            assert!(block_operands.len() <= 2);
340
341            if !block_operands.is_empty() {
342                if block_operands.len() == 1 {
343                    write!(&mut s, " {}", block_operands[0].name).unwrap();
344                } else {
345                    let blocks: Vec<_> = block_operands.iter().map(|o| o.name).collect();
346                    let blocks = blocks.join(" ");
347                    write!(
348                        &mut s,
349                        " (block_array_{} {})",
350                        inst.format.num_block_operands, blocks,
351                    )
352                    .unwrap();
353                }
354            }
355
356            s.push_str("))");
357            fmt.line(&s);
358        });
359        fmt.line(")");
360
361        // Generate a constructor if this is the mid-end prelude.
362        if isle_target == IsleTarget::Opt {
363            fmtln!(
364                fmt,
365                "(rule ({}{} {})",
366                inst.name,
367                if ty_in_decl { " ty" } else { "" },
368                inst.operands_in
369                    .iter()
370                    .map(|o| o.name)
371                    .collect::<Vec<_>>()
372                    .join(" ")
373            );
374            fmt.indent(|fmt| {
375                let mut s = format!(
376                    "({make_inst_ctor}{} (InstructionData.{} (Opcode.{})",
377                    if ty_in_decl { " ty" } else { "" },
378                    inst.format.name,
379                    inst.camel_name
380                );
381
382                // Handle values. Note that we skip generating
383                // constructors for any instructions with variadic
384                // value lists. This is fine for the mid-end because
385                // in practice only calls and branches (for branch
386                // args) use this functionality, and neither can
387                // really be optimized or rewritten in the mid-end
388                // (currently).
389                //
390                // As a consequence, we only have to handle the
391                // one-`Value` case, in which the `Value` is directly
392                // in the `InstructionData`, and the multiple-`Value`
393                // case, in which the `Value`s are in a
394                // statically-sized array (e.g. `[Value; 2]` for a
395                // binary op).
396                assert!(!inst.format.has_value_list);
397                if inst.format.num_value_operands == 1 {
398                    write!(
399                        &mut s,
400                        " {}",
401                        inst.operands_in.iter().find(|o| o.is_value()).unwrap().name
402                    )
403                    .unwrap();
404                } else if inst.format.num_value_operands > 1 {
405                    // As above, get all bindings together, and pass
406                    // to a sub-term; here we use a constructor to
407                    // build the value array.
408                    let values = inst
409                        .operands_in
410                        .iter()
411                        .filter(|o| o.is_value())
412                        .map(|o| o.name)
413                        .collect::<Vec<_>>();
414                    assert_eq!(values.len(), inst.format.num_value_operands);
415                    let values = values.join(" ");
416                    write!(
417                        &mut s,
418                        " (value_array_{}_ctor {})",
419                        inst.format.num_value_operands, values
420                    )
421                    .unwrap();
422                }
423
424                if inst.format.num_block_operands > 0 {
425                    let blocks: Vec<_> = inst
426                        .operands_in
427                        .iter()
428                        .filter(|o| o.kind.is_block())
429                        .map(|o| o.name)
430                        .collect();
431                    if inst.format.num_block_operands == 1 {
432                        write!(&mut s, " {}", blocks.first().unwrap(),).unwrap();
433                    } else {
434                        write!(
435                            &mut s,
436                            " (block_array_{} {})",
437                            inst.format.num_block_operands,
438                            blocks.join(" ")
439                        )
440                        .unwrap();
441                    }
442                }
443
444                // Immediates (non-value args).
445                for o in inst
446                    .operands_in
447                    .iter()
448                    .filter(|o| !o.is_value() && !o.is_varargs() && !o.kind.is_block())
449                {
450                    write!(&mut s, " {}", o.name).unwrap();
451                }
452                s.push_str("))");
453                fmt.line(&s);
454            });
455            fmt.line(")");
456        }
457
458        fmt.empty_line();
459    }
460}
461
462fn gen_opt_isle(
463    formats: &[Rc<InstructionFormat>],
464    instructions: &AllInstructions,
465    fmt: &mut Formatter,
466) {
467    gen_common_isle(formats, instructions, fmt, IsleTarget::Opt);
468}
469
470fn gen_lower_isle(
471    formats: &[Rc<InstructionFormat>],
472    instructions: &AllInstructions,
473    fmt: &mut Formatter,
474) {
475    gen_common_isle(formats, instructions, fmt, IsleTarget::Lower);
476}
477
478/// Generate an `enum` immediate in ISLE.
479fn gen_isle_enum(name: &str, mut variants: Vec<&str>, fmt: &mut Formatter) {
480    variants.sort();
481    let prefix = format!(";;;; Enumerated Immediate: {name} ");
482    fmtln!(fmt, "{:;<80}", prefix);
483    fmt.empty_line();
484    fmtln!(fmt, "(type {} extern", name);
485    fmt.indent(|fmt| {
486        fmt.line("(enum");
487        fmt.indent(|fmt| {
488            for variant in variants {
489                fmtln!(fmt, "{}", variant);
490            }
491        });
492        fmt.line(")");
493    });
494    fmt.line(")");
495    fmt.empty_line();
496}
497
498pub(crate) fn generate(
499    formats: &[Rc<InstructionFormat>],
500    all_inst: &AllInstructions,
501    isle_opt_filename: &str,
502    isle_lower_filename: &str,
503    isle_dir: &std::path::Path,
504) -> Result<(), error::Error> {
505    // ISLE DSL: mid-end ("opt") generated bindings.
506    let mut fmt = Formatter::new(Language::Isle);
507    gen_opt_isle(&formats, all_inst, &mut fmt);
508    fmt.write(isle_opt_filename, isle_dir)?;
509
510    // ISLE DSL: lowering generated bindings.
511    let mut fmt = Formatter::new(Language::Isle);
512    gen_lower_isle(&formats, all_inst, &mut fmt);
513    fmt.write(isle_lower_filename, isle_dir)?;
514
515    Ok(())
516}