cranelift_codegen_meta/
gen_isle.rs

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