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    let ret_ty = match isle_target {
212        IsleTarget::Lower => "Inst",
213        IsleTarget::Opt => "Value",
214    };
215    for inst in instructions {
216        if isle_target == IsleTarget::Opt
217            && (inst.format.has_value_list || inst.value_results.len() != 1)
218        {
219            continue;
220        }
221
222        fmtln!(
223            fmt,
224            "(decl {} ({}{}) {})",
225            inst.name,
226            match isle_target {
227                IsleTarget::Lower => "",
228                IsleTarget::Opt => "Type ",
229            },
230            inst.operands_in
231                .iter()
232                .map(|o| {
233                    let ty = o.kind.rust_type;
234                    if ty == "&[Value]" {
235                        "ValueSlice"
236                    } else {
237                        ty.rsplit("::").next().unwrap()
238                    }
239                })
240                .collect::<Vec<_>>()
241                .join(" "),
242            ret_ty
243        );
244        fmtln!(fmt, "(extractor");
245        fmt.indent(|fmt| {
246            fmtln!(
247                fmt,
248                "({} {}{})",
249                inst.name,
250                match isle_target {
251                    IsleTarget::Lower => "",
252                    IsleTarget::Opt => "ty ",
253                },
254                inst.operands_in
255                    .iter()
256                    .map(|o| { o.name })
257                    .collect::<Vec<_>>()
258                    .join(" ")
259            );
260
261            let mut s = format!(
262                "(inst_data{} (InstructionData.{} (Opcode.{})",
263                match isle_target {
264                    IsleTarget::Lower => "",
265                    IsleTarget::Opt => " ty",
266                },
267                inst.format.name,
268                inst.camel_name
269            );
270
271            // Value and varargs operands.
272            if inst.format.has_value_list {
273                // The instruction format uses a value list, but the
274                // instruction itself might have not only a `&[Value]`
275                // varargs operand, but also one or more `Value` operands as
276                // well. If this is the case, then we need to read them off
277                // the front of the `ValueList`.
278                let values: Vec<_> = inst
279                    .operands_in
280                    .iter()
281                    .filter(|o| o.is_value())
282                    .map(|o| o.name)
283                    .collect();
284                let varargs = inst
285                    .operands_in
286                    .iter()
287                    .find(|o| o.is_varargs())
288                    .unwrap()
289                    .name;
290                if values.is_empty() {
291                    write!(&mut s, " (value_list_slice {varargs})").unwrap();
292                } else {
293                    write!(
294                        &mut s,
295                        " (unwrap_head_value_list_{} {} {})",
296                        values.len(),
297                        values.join(" "),
298                        varargs
299                    )
300                    .unwrap();
301                }
302            } else if inst.format.num_value_operands == 1 {
303                write!(
304                    &mut s,
305                    " {}",
306                    inst.operands_in.iter().find(|o| o.is_value()).unwrap().name
307                )
308                .unwrap();
309            } else if inst.format.num_value_operands > 1 {
310                let values = inst
311                    .operands_in
312                    .iter()
313                    .filter(|o| o.is_value())
314                    .map(|o| o.name)
315                    .collect::<Vec<_>>();
316                assert_eq!(values.len(), inst.format.num_value_operands);
317                let values = values.join(" ");
318                write!(
319                    &mut s,
320                    " (value_array_{} {})",
321                    inst.format.num_value_operands, values,
322                )
323                .unwrap();
324            }
325
326            // Immediates.
327            let imm_operands: Vec<_> = inst
328                .operands_in
329                .iter()
330                .filter(|o| !o.is_value() && !o.is_varargs() && !o.kind.is_block())
331                .collect();
332            assert_eq!(imm_operands.len(), inst.format.imm_fields.len(),);
333            for op in imm_operands {
334                write!(&mut s, " {}", op.name).unwrap();
335            }
336
337            // Blocks.
338            let block_operands: Vec<_> = inst
339                .operands_in
340                .iter()
341                .filter(|o| o.kind.is_block())
342                .collect();
343            assert_eq!(block_operands.len(), inst.format.num_block_operands);
344            assert!(block_operands.len() <= 2);
345
346            if !block_operands.is_empty() {
347                if block_operands.len() == 1 {
348                    write!(&mut s, " {}", block_operands[0].name).unwrap();
349                } else {
350                    let blocks: Vec<_> = block_operands.iter().map(|o| o.name).collect();
351                    let blocks = blocks.join(" ");
352                    write!(
353                        &mut s,
354                        " (block_array_{} {})",
355                        inst.format.num_block_operands, blocks,
356                    )
357                    .unwrap();
358                }
359            }
360
361            s.push_str("))");
362            fmt.line(&s);
363        });
364        fmt.line(")");
365
366        // Generate a constructor if this is the mid-end prelude.
367        if isle_target == IsleTarget::Opt {
368            fmtln!(
369                fmt,
370                "(rule ({} ty {})",
371                inst.name,
372                inst.operands_in
373                    .iter()
374                    .map(|o| o.name)
375                    .collect::<Vec<_>>()
376                    .join(" ")
377            );
378            fmt.indent(|fmt| {
379                let mut s = format!(
380                    "(make_inst ty (InstructionData.{} (Opcode.{})",
381                    inst.format.name, inst.camel_name
382                );
383
384                // Handle values. Note that we skip generating
385                // constructors for any instructions with variadic
386                // value lists. This is fine for the mid-end because
387                // in practice only calls and branches (for branch
388                // args) use this functionality, and neither can
389                // really be optimized or rewritten in the mid-end
390                // (currently).
391                //
392                // As a consequence, we only have to handle the
393                // one-`Value` case, in which the `Value` is directly
394                // in the `InstructionData`, and the multiple-`Value`
395                // case, in which the `Value`s are in a
396                // statically-sized array (e.g. `[Value; 2]` for a
397                // binary op).
398                assert!(!inst.format.has_value_list);
399                if inst.format.num_value_operands == 1 {
400                    write!(
401                        &mut s,
402                        " {}",
403                        inst.operands_in.iter().find(|o| o.is_value()).unwrap().name
404                    )
405                    .unwrap();
406                } else if inst.format.num_value_operands > 1 {
407                    // As above, get all bindings together, and pass
408                    // to a sub-term; here we use a constructor to
409                    // build the value array.
410                    let values = inst
411                        .operands_in
412                        .iter()
413                        .filter(|o| o.is_value())
414                        .map(|o| o.name)
415                        .collect::<Vec<_>>();
416                    assert_eq!(values.len(), inst.format.num_value_operands);
417                    let values = values.join(" ");
418                    write!(
419                        &mut s,
420                        " (value_array_{}_ctor {})",
421                        inst.format.num_value_operands, values
422                    )
423                    .unwrap();
424                }
425
426                if inst.format.num_block_operands > 0 {
427                    let blocks: Vec<_> = inst
428                        .operands_in
429                        .iter()
430                        .filter(|o| o.kind.is_block())
431                        .map(|o| o.name)
432                        .collect();
433                    if inst.format.num_block_operands == 1 {
434                        write!(&mut s, " {}", blocks.first().unwrap(),).unwrap();
435                    } else {
436                        write!(
437                            &mut s,
438                            " (block_array_{} {})",
439                            inst.format.num_block_operands,
440                            blocks.join(" ")
441                        )
442                        .unwrap();
443                    }
444                }
445
446                // Immediates (non-value args).
447                for o in inst
448                    .operands_in
449                    .iter()
450                    .filter(|o| !o.is_value() && !o.is_varargs() && !o.kind.is_block())
451                {
452                    write!(&mut s, " {}", o.name).unwrap();
453                }
454                s.push_str("))");
455                fmt.line(&s);
456            });
457            fmt.line(")");
458        }
459
460        fmt.empty_line();
461    }
462}
463
464fn gen_opt_isle(
465    formats: &[Rc<InstructionFormat>],
466    instructions: &AllInstructions,
467    fmt: &mut Formatter,
468) {
469    gen_common_isle(formats, instructions, fmt, IsleTarget::Opt);
470}
471
472fn gen_lower_isle(
473    formats: &[Rc<InstructionFormat>],
474    instructions: &AllInstructions,
475    fmt: &mut Formatter,
476) {
477    gen_common_isle(formats, instructions, fmt, IsleTarget::Lower);
478}
479
480/// Generate an `enum` immediate in ISLE.
481fn gen_isle_enum(name: &str, mut variants: Vec<&str>, fmt: &mut Formatter) {
482    variants.sort();
483    let prefix = format!(";;;; Enumerated Immediate: {name} ");
484    fmtln!(fmt, "{:;<80}", prefix);
485    fmt.empty_line();
486    fmtln!(fmt, "(type {} extern", name);
487    fmt.indent(|fmt| {
488        fmt.line("(enum");
489        fmt.indent(|fmt| {
490            for variant in variants {
491                fmtln!(fmt, "{}", variant);
492            }
493        });
494        fmt.line(")");
495    });
496    fmt.line(")");
497    fmt.empty_line();
498}
499
500pub(crate) fn generate(
501    formats: &[Rc<InstructionFormat>],
502    all_inst: &AllInstructions,
503    isle_opt_filename: &str,
504    isle_lower_filename: &str,
505    isle_dir: &std::path::Path,
506) -> Result<(), error::Error> {
507    // ISLE DSL: mid-end ("opt") generated bindings.
508    let mut fmt = Formatter::new(Language::Isle);
509    gen_opt_isle(&formats, all_inst, &mut fmt);
510    fmt.write(isle_opt_filename, isle_dir)?;
511
512    // ISLE DSL: lowering generated bindings.
513    let mut fmt = Formatter::new(Language::Isle);
514    gen_lower_isle(&formats, all_inst, &mut fmt);
515    fmt.write(isle_lower_filename, isle_dir)?;
516
517    Ok(())
518}