Skip to main content

cranelift_codegen_meta/
gen_isle.rs

1use crate::cdsl::formats::InstructionFormat;
2use crate::cdsl::instructions::AllInstructions;
3use crate::error;
4use cranelift_srcgen::{Formatter, Language, fmtln};
5use std::{borrow::Cow, cmp::Ordering, 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    // Also declare the MemFlagsData type, which is the resolved form of MemFlags.
65    // MemFlags is an entity index into the DFG's MemFlagsSet, while MemFlagsData
66    // contains the actual flag bits. Backend MachInst types use MemFlagsData.
67    fmt.line("(type MemFlagsData (primitive MemFlagsData))");
68    fmt.empty_line();
69
70    // Generate the `enum` immediates, expanding all of the available variants
71    // into ISLE.
72    for (name, field) in enums {
73        let field = field.enum_values().expect("only enums considered here");
74        let variants = field.values().cloned().collect();
75        gen_isle_enum(name, variants, fmt)
76    }
77
78    // Generate all of the value arrays we need for `InstructionData` as well as
79    // the constructors and extractors for them.
80    fmt.line(";;;; Value Arrays ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
81    fmt.empty_line();
82    let value_array_arities: BTreeSet<_> = formats
83        .iter()
84        .filter(|f| f.typevar_operand.is_some() && !f.has_value_list && f.num_value_operands != 1)
85        .map(|f| f.num_value_operands)
86        .collect();
87    for n in value_array_arities {
88        fmtln!(fmt, ";; ISLE representation of `[Value; {}]`.", n);
89        fmtln!(fmt, "(type ValueArray{} extern (enum))", n);
90        fmt.empty_line();
91
92        fmtln!(
93            fmt,
94            "(decl value_array_{} ({}) ValueArray{})",
95            n,
96            (0..n).map(|_| "Value").collect::<Vec<_>>().join(" "),
97            n
98        );
99        fmtln!(
100            fmt,
101            "(extern constructor value_array_{} pack_value_array_{})",
102            n,
103            n
104        );
105        fmtln!(
106            fmt,
107            "(extern extractor infallible value_array_{} unpack_value_array_{})",
108            n,
109            n
110        );
111        fmt.empty_line();
112    }
113
114    // Generate all of the block arrays we need for `InstructionData` as well as
115    // the constructors and extractors for them.
116    fmt.line(";;;; Block Arrays ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
117    fmt.empty_line();
118    let block_array_arities: BTreeSet<_> = formats
119        .iter()
120        .filter(|f| f.num_block_operands > 1)
121        .map(|f| f.num_block_operands)
122        .collect();
123    for n in block_array_arities {
124        fmtln!(fmt, ";; ISLE representation of `[BlockCall; {}]`.", n);
125        fmtln!(fmt, "(type BlockArray{} extern (enum))", n);
126        fmt.empty_line();
127
128        fmtln!(
129            fmt,
130            "(decl block_array_{0} ({1}) BlockArray{0})",
131            n,
132            (0..n).map(|_| "BlockCall").collect::<Vec<_>>().join(" ")
133        );
134
135        fmtln!(
136            fmt,
137            "(extern constructor block_array_{0} pack_block_array_{0})",
138            n
139        );
140
141        fmtln!(
142            fmt,
143            "(extern extractor infallible block_array_{0} unpack_block_array_{0})",
144            n
145        );
146        fmt.empty_line();
147    }
148
149    // Raw block entities.
150    fmtln!(fmt, "(type Block extern (enum))");
151    fmt.empty_line();
152
153    // Generate the extern type declaration for `Opcode`.
154    fmt.line(";;;; `Opcode` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
155    fmt.empty_line();
156    fmt.line("(type Opcode extern");
157    fmt.indent(|fmt| {
158        fmt.line("(enum");
159        fmt.indent(|fmt| {
160            for inst in instructions {
161                fmtln!(fmt, "{}", inst.camel_name);
162            }
163        });
164        fmt.line(")");
165    });
166    fmt.line(")");
167    fmt.empty_line();
168
169    // Generate the extern type declaration for `InstructionData`.
170    fmtln!(
171        fmt,
172        ";;;; `InstructionData` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
173    );
174    fmt.empty_line();
175    fmtln!(fmt, "(type InstructionData extern");
176    fmt.indent(|fmt| {
177        fmt.line("(enum");
178        fmt.indent(|fmt| {
179            for format in formats {
180                let mut s = format!("({} (opcode Opcode)", format.name);
181                if format.has_value_list {
182                    s.push_str(" (args ValueList)");
183                } else if format.num_value_operands == 1 {
184                    s.push_str(" (arg Value)");
185                } else if format.num_value_operands > 1 {
186                    write!(&mut s, " (args ValueArray{})", format.num_value_operands).unwrap();
187                }
188
189                match format.num_block_operands {
190                    0 => (),
191                    1 => write!(&mut s, " (destination BlockCall)").unwrap(),
192                    n => write!(&mut s, " (blocks BlockArray{n})").unwrap(),
193                }
194
195                match format.num_raw_block_operands {
196                    0 => (),
197                    1 => write!(&mut s, "(block Block)").unwrap(),
198                    _ => panic!("Too many raw block arguments"),
199                }
200
201                for field in &format.imm_fields {
202                    write!(
203                        &mut s,
204                        " ({} {})",
205                        field.member,
206                        field.kind.rust_type.rsplit("::").next().unwrap()
207                    )
208                    .unwrap();
209                }
210                s.push(')');
211                fmt.line(&s);
212            }
213        });
214        fmt.line(")");
215    });
216    fmt.line(")");
217    fmt.empty_line();
218
219    // Generate the helper extractors for each opcode's full instruction.
220    fmtln!(
221        fmt,
222        ";;;; Extracting Opcode, Operands, and Immediates from `InstructionData` ;;;;;;;;",
223    );
224    fmt.empty_line();
225    for inst in instructions {
226        let results_len = inst.value_results.len();
227        let is_var_args = inst.format.has_value_list;
228        let has_side_effects = inst.can_trap || inst.other_side_effects;
229
230        let (ret_ty, ty_in_decl, make_inst_ctor, inst_data_etor) =
231            match (isle_target, is_var_args, results_len, has_side_effects) {
232                // The mid-end does not deal with instructions that have var-args right now.
233                (IsleTarget::Opt, true, _, _) => continue,
234
235                (IsleTarget::Opt, _, 1, false) => ("Value", true, "make_inst", "inst_data_value"),
236                (IsleTarget::Opt, _, _, _) => ("Inst", false, "make_skeleton_inst", "inst_data"),
237                (IsleTarget::Lower, false, r, _) if r >= 1 => {
238                    ("Inst", true, "make_inst", "inst_data_value")
239                }
240                (IsleTarget::Lower, _, _, _) => ("Inst", false, "make_inst", "inst_data_value"),
241            };
242
243        fmtln!(
244            fmt,
245            "(decl {} ({}{}) {})",
246            inst.name,
247            if ty_in_decl { "Type " } else { "" },
248            inst.operands_in
249                .iter()
250                .map(|o| {
251                    let ty = o.kind.rust_type;
252                    if ty == "&[Value]" {
253                        "ValueSlice"
254                    } else {
255                        ty.rsplit("::").next().unwrap()
256                    }
257                })
258                .collect::<Vec<_>>()
259                .join(" "),
260            ret_ty
261        );
262        fmtln!(fmt, "(extractor");
263        fmt.indent(|fmt| {
264            fmtln!(
265                fmt,
266                "({} {}{})",
267                inst.name,
268                if ty_in_decl { "ty " } else { "" },
269                inst.operands_in
270                    .iter()
271                    .map(|o| { o.name })
272                    .collect::<Vec<_>>()
273                    .join(" ")
274            );
275
276            let mut s = format!(
277                "({inst_data_etor} {}(InstructionData.{} (Opcode.{})",
278                if ty_in_decl {
279                    "ty "
280                } else if isle_target == IsleTarget::Lower {
281                    "_ "
282                } else {
283                    ""
284                },
285                inst.format.name,
286                inst.camel_name
287            );
288
289            // Value and varargs operands.
290            if inst.format.has_value_list {
291                // The instruction format uses a value list, but the
292                // instruction itself might have not only a `&[Value]`
293                // varargs operand, but also one or more `Value` operands as
294                // well. If this is the case, then we need to read them off
295                // the front of the `ValueList`.
296                let values: Vec<_> = inst
297                    .operands_in
298                    .iter()
299                    .filter(|o| o.is_value())
300                    .map(|o| o.name)
301                    .collect();
302                let varargs = inst
303                    .operands_in
304                    .iter()
305                    .find(|o| o.is_varargs())
306                    .unwrap()
307                    .name;
308                if values.is_empty() {
309                    write!(&mut s, " (value_list_slice {varargs})").unwrap();
310                } else {
311                    write!(
312                        &mut s,
313                        " (unwrap_head_value_list_{} {} {})",
314                        values.len(),
315                        values.join(" "),
316                        varargs
317                    )
318                    .unwrap();
319                }
320            } else if inst.format.num_value_operands == 1 {
321                write!(
322                    &mut s,
323                    " {}",
324                    inst.operands_in.iter().find(|o| o.is_value()).unwrap().name
325                )
326                .unwrap();
327            } else if inst.format.num_value_operands > 1 {
328                let values = inst
329                    .operands_in
330                    .iter()
331                    .filter(|o| o.is_value())
332                    .map(|o| o.name)
333                    .collect::<Vec<_>>();
334                assert_eq!(values.len(), inst.format.num_value_operands);
335                let values = values.join(" ");
336                write!(
337                    &mut s,
338                    " (value_array_{} {})",
339                    inst.format.num_value_operands, values,
340                )
341                .unwrap();
342            }
343
344            // Immediates.
345            let imm_operands: Vec<_> = inst
346                .operands_in
347                .iter()
348                .filter(|o| {
349                    !o.is_value() && !o.is_varargs() && !o.kind.is_block() && !o.kind.is_raw_block()
350                })
351                .collect();
352            assert_eq!(imm_operands.len(), inst.format.imm_fields.len(),);
353            for op in imm_operands {
354                write!(&mut s, " {}", op.name).unwrap();
355            }
356
357            // Blocks.
358            let block_operands: Vec<_> = inst
359                .operands_in
360                .iter()
361                .filter(|o| o.kind.is_block())
362                .collect();
363            assert_eq!(block_operands.len(), inst.format.num_block_operands);
364            assert!(block_operands.len() <= 2);
365
366            if !block_operands.is_empty() {
367                if block_operands.len() == 1 {
368                    write!(&mut s, " {}", block_operands[0].name).unwrap();
369                } else {
370                    let blocks: Vec<_> = block_operands.iter().map(|o| o.name).collect();
371                    let blocks = blocks.join(" ");
372                    write!(
373                        &mut s,
374                        " (block_array_{} {})",
375                        inst.format.num_block_operands, blocks,
376                    )
377                    .unwrap();
378                }
379            }
380
381            // Raw blocks.
382            match inst.format.num_raw_block_operands {
383                0 => {}
384                1 => {
385                    write!(&mut s, " block").unwrap();
386                }
387                _ => panic!("Too many raw block arguments"),
388            }
389
390            s.push_str("))");
391            fmt.line(&s);
392        });
393        fmt.line(")");
394
395        // Generate a constructor if this is the mid-end prelude.
396        if isle_target == IsleTarget::Opt {
397            fmtln!(
398                fmt,
399                "(rule ({}{} {})",
400                inst.name,
401                if ty_in_decl { " ty" } else { "" },
402                inst.operands_in
403                    .iter()
404                    .map(|o| o.name)
405                    .collect::<Vec<_>>()
406                    .join(" ")
407            );
408            fmt.indent(|fmt| {
409                let mut s = format!(
410                    "({make_inst_ctor}{} (InstructionData.{} (Opcode.{})",
411                    if ty_in_decl { " ty" } else { "" },
412                    inst.format.name,
413                    inst.camel_name
414                );
415
416                // Handle values. Note that we skip generating
417                // constructors for any instructions with variadic
418                // value lists. This is fine for the mid-end because
419                // in practice only calls and branches (for branch
420                // args) use this functionality, and neither can
421                // really be optimized or rewritten in the mid-end
422                // (currently).
423                //
424                // As a consequence, we only have to handle the
425                // one-`Value` case, in which the `Value` is directly
426                // in the `InstructionData`, and the multiple-`Value`
427                // case, in which the `Value`s are in a
428                // statically-sized array (e.g. `[Value; 2]` for a
429                // binary op).
430                assert!(!inst.format.has_value_list);
431                if inst.format.num_value_operands == 1 {
432                    write!(
433                        &mut s,
434                        " {}",
435                        inst.operands_in.iter().find(|o| o.is_value()).unwrap().name
436                    )
437                    .unwrap();
438                } else if inst.format.num_value_operands > 1 {
439                    // As above, get all bindings together, and pass
440                    // to a sub-term; here we use a constructor to
441                    // build the value array.
442                    let values = inst
443                        .operands_in
444                        .iter()
445                        .filter(|o| o.is_value())
446                        .map(|o| o.name)
447                        .collect::<Vec<_>>();
448                    assert_eq!(values.len(), inst.format.num_value_operands);
449                    let values = values.join(" ");
450                    write!(
451                        &mut s,
452                        " (value_array_{}_ctor {})",
453                        inst.format.num_value_operands, values
454                    )
455                    .unwrap();
456                }
457
458                if inst.format.num_block_operands > 0 {
459                    let blocks: Vec<_> = inst
460                        .operands_in
461                        .iter()
462                        .filter(|o| o.kind.is_block())
463                        .map(|o| o.name)
464                        .collect();
465                    if inst.format.num_block_operands == 1 {
466                        write!(&mut s, " {}", blocks.first().unwrap(),).unwrap();
467                    } else {
468                        write!(
469                            &mut s,
470                            " (block_array_{} {})",
471                            inst.format.num_block_operands,
472                            blocks.join(" ")
473                        )
474                        .unwrap();
475                    }
476                }
477
478                match inst.format.num_raw_block_operands {
479                    0 => {}
480                    1 => {
481                        write!(&mut s, " block").unwrap();
482                    }
483                    _ => panic!("Too many raw block arguments"),
484                }
485
486                // Immediates (non-value args).
487                for o in inst.operands_in.iter().filter(|o| {
488                    !o.is_value() && !o.is_varargs() && !o.kind.is_block() && !o.kind.is_raw_block()
489                }) {
490                    write!(&mut s, " {}", o.name).unwrap();
491                }
492                s.push_str("))");
493                fmt.line(&s);
494            });
495            fmt.line(")");
496        }
497
498        fmt.empty_line();
499    }
500}
501
502fn gen_opt_isle(
503    formats: &[Rc<InstructionFormat>],
504    instructions: &AllInstructions,
505    fmt: &mut Formatter,
506) {
507    gen_common_isle(formats, instructions, fmt, IsleTarget::Opt);
508}
509
510fn gen_lower_isle(
511    formats: &[Rc<InstructionFormat>],
512    instructions: &AllInstructions,
513    fmt: &mut Formatter,
514) {
515    gen_common_isle(formats, instructions, fmt, IsleTarget::Lower);
516}
517
518/// Generate an `enum` immediate in ISLE.
519fn gen_isle_enum(name: &str, mut variants: Vec<&str>, fmt: &mut Formatter) {
520    variants.sort();
521    let prefix = format!(";;;; Enumerated Immediate: {name} ");
522    fmtln!(fmt, "{:;<80}", prefix);
523    fmt.empty_line();
524    fmtln!(fmt, "(type {} extern", name);
525    fmt.indent(|fmt| {
526        fmt.line("(enum");
527        fmt.indent(|fmt| {
528            for variant in variants {
529                fmtln!(fmt, "{}", variant);
530            }
531        });
532        fmt.line(")");
533    });
534    fmt.line(")");
535    fmt.empty_line();
536}
537
538#[derive(Clone, Copy, PartialEq, Eq)]
539struct NumericType {
540    signed: bool,
541    byte_width: u8,
542}
543
544impl NumericType {
545    fn all() -> impl Iterator<Item = NumericType> {
546        [1, 2, 4, 8, 16].into_iter().flat_map(|byte_width| {
547            [true, false]
548                .into_iter()
549                .map(move |signed| NumericType { signed, byte_width })
550        })
551    }
552
553    fn name(&self) -> &'static str {
554        let idx = self.byte_width.ilog2();
555        let idx = usize::try_from(idx).unwrap();
556        if self.signed {
557            ["i8", "i16", "i32", "i64", "i128"][idx]
558        } else {
559            ["u8", "u16", "u32", "u64", "u128"][idx]
560        }
561    }
562}
563
564#[derive(Clone, Default, PartialEq, Eq)]
565struct NumericOp<'a> {
566    /// The name of this operation.
567    name: &'a str,
568    /// The return type of this operation.
569    ret: &'a str,
570    /// Whether this operation is partial.
571    partial: bool,
572    /// (name, type) pairs of arguments.
573    args: Rc<[(&'a str, &'a str)]>,
574    /// The source text for the constructor's body.
575    body: &'a str,
576    /// Whether extractors should be generated for this op.
577    ///
578    /// Must have `arity == 1`, `ret == bool`, and `name.starts_with("is_")`.
579    etors: bool,
580}
581
582impl NumericOp<'_> {
583    fn ops_for_type(ty: &NumericType) -> impl Iterator<Item = NumericOp<'_>> {
584        let arity1 = NumericOp {
585            args: [("a", ty.name())].into(),
586            ..NumericOp::default()
587        };
588
589        let arity2 = NumericOp {
590            args: [("a", ty.name()), ("b", ty.name())].into(),
591            ..NumericOp::default()
592        };
593
594        let comparison = NumericOp {
595            ret: "bool",
596            ..arity2.clone()
597        };
598
599        let predicate = NumericOp {
600            ret: "bool",
601            etors: true,
602            ..arity1.clone()
603        };
604
605        let binop = NumericOp {
606            ret: ty.name(),
607            ..arity2.clone()
608        };
609
610        let partial_binop = NumericOp {
611            ret: ty.name(),
612            partial: true,
613            ..binop.clone()
614        };
615
616        let unop = NumericOp {
617            ret: ty.name(),
618            ..arity1.clone()
619        };
620
621        let partial_unop = NumericOp {
622            ret: ty.name(),
623            partial: true,
624            ..unop.clone()
625        };
626
627        let shift = NumericOp {
628            args: [("a", ty.name()), ("b", "u32")].into(),
629            ..binop.clone()
630        };
631
632        let partial_shift = NumericOp {
633            args: [("a", ty.name()), ("b", "u32")].into(),
634            ..partial_binop.clone()
635        };
636
637        // Operations that apply to both signed and unsigned numbers.
638        let ops = [
639            // Comparisons.
640            NumericOp {
641                name: "eq",
642                body: "a == b",
643                ..comparison.clone()
644            },
645            NumericOp {
646                name: "ne",
647                body: "a != b",
648                ..comparison.clone()
649            },
650            NumericOp {
651                name: "lt",
652                body: "a < b",
653                ..comparison.clone()
654            },
655            NumericOp {
656                name: "lt_eq",
657                body: "a <= b",
658                ..comparison.clone()
659            },
660            NumericOp {
661                name: "gt",
662                body: "a > b",
663                ..comparison.clone()
664            },
665            NumericOp {
666                name: "gt_eq",
667                body: "a >= b",
668                ..comparison.clone()
669            },
670            // Arithmetic operations.
671            //
672            // For each operation (e.g. addition) we have three variants:
673            //
674            // * partial ctor `checked_add`: no return value on overflow
675            // * ctor `wrapping_add`: wraps on overflow
676            // * ctor `add`: non-partial but panics at runtime on overflow
677            NumericOp {
678                name: "checked_add",
679                body: "a.checked_add(b)",
680                ..partial_binop.clone()
681            },
682            NumericOp {
683                name: "wrapping_add",
684                body: "a.wrapping_add(b)",
685                ..binop.clone()
686            },
687            NumericOp {
688                name: "add",
689                body: r#"a.checked_add(b).unwrap_or_else(|| panic!("addition overflow: {a} + {b}"))"#,
690                ..binop.clone()
691            },
692            NumericOp {
693                name: "checked_sub",
694                body: "a.checked_sub(b)",
695                ..partial_binop.clone()
696            },
697            NumericOp {
698                name: "wrapping_sub",
699                body: "a.wrapping_sub(b)",
700                ..binop.clone()
701            },
702            NumericOp {
703                name: "sub",
704                body: r#"a.checked_sub(b).unwrap_or_else(|| panic!("subtraction overflow: {a} - {b}"))"#,
705                ..binop.clone()
706            },
707            NumericOp {
708                name: "checked_mul",
709                body: "a.checked_mul(b)",
710                ..partial_binop.clone()
711            },
712            NumericOp {
713                name: "wrapping_mul",
714                body: "a.wrapping_mul(b)",
715                ..binop.clone()
716            },
717            NumericOp {
718                name: "mul",
719                body: r#"a.checked_mul(b).unwrap_or_else(|| panic!("multiplication overflow: {a} * {b}"))"#,
720                ..binop.clone()
721            },
722            NumericOp {
723                name: "checked_div",
724                body: "a.checked_div(b)",
725                ..partial_binop.clone()
726            },
727            NumericOp {
728                name: "wrapping_div",
729                body: "a.wrapping_div(b)",
730                ..binop.clone()
731            },
732            NumericOp {
733                name: "div",
734                body: r#"a.checked_div(b).unwrap_or_else(|| panic!("div failure: {a} / {b}"))"#,
735                ..binop.clone()
736            },
737            NumericOp {
738                name: "checked_rem",
739                body: "a.checked_rem(b)",
740                ..partial_binop.clone()
741            },
742            NumericOp {
743                name: "rem",
744                body: r#"a.checked_rem(b).unwrap_or_else(|| panic!("rem failure: {a} % {b}"))"#,
745                ..binop.clone()
746            },
747            // Bitwise operations.
748            //
749            // When applicable (e.g. shifts) we have checked, wrapping, and
750            // unwrapping variants, similar to arithmetic operations.
751            NumericOp {
752                name: "and",
753                body: "a & b",
754                ..binop.clone()
755            },
756            NumericOp {
757                name: "or",
758                body: "a | b",
759                ..binop.clone()
760            },
761            NumericOp {
762                name: "xor",
763                body: "a ^ b",
764                ..binop.clone()
765            },
766            NumericOp {
767                name: "not",
768                body: "!a",
769                ..unop.clone()
770            },
771            NumericOp {
772                name: "checked_shl",
773                body: "a.checked_shl(b)",
774                ..partial_shift.clone()
775            },
776            NumericOp {
777                name: "wrapping_shl",
778                body: "a.wrapping_shl(b)",
779                ..shift.clone()
780            },
781            NumericOp {
782                name: "shl",
783                body: r#"a.checked_shl(b).unwrap_or_else(|| panic!("shl overflow: {a} << {b}"))"#,
784                ..shift.clone()
785            },
786            NumericOp {
787                name: "checked_shr",
788                body: "a.checked_shr(b)",
789                ..partial_shift.clone()
790            },
791            NumericOp {
792                name: "wrapping_shr",
793                body: "a.wrapping_shr(b)",
794                ..shift.clone()
795            },
796            NumericOp {
797                name: "shr",
798                body: r#"a.checked_shr(b).unwrap_or_else(|| panic!("shr overflow: {a} >> {b}"))"#,
799                ..shift.clone()
800            },
801            NumericOp {
802                name: "rotl",
803                body: "a.rotate_left(b)",
804                ..shift.clone()
805            },
806            NumericOp {
807                name: "rotr",
808                body: "a.rotate_right(b)",
809                ..shift.clone()
810            },
811            // Predicates.
812            //
813            // We generate both pure constructors and a variety of extractors
814            // for these. See the relevant comments in `gen_numerics_isle` about
815            // the extractors.
816            NumericOp {
817                name: "is_zero",
818                body: "a == 0",
819                ..predicate.clone()
820            },
821            NumericOp {
822                name: "is_non_zero",
823                body: "a != 0",
824                ..predicate.clone()
825            },
826            NumericOp {
827                name: "is_odd",
828                body: "a & 1 == 1",
829                ..predicate.clone()
830            },
831            NumericOp {
832                name: "is_even",
833                body: "a & 1 == 0",
834                ..predicate.clone()
835            },
836            // Miscellaneous unary operations.
837            NumericOp {
838                name: "checked_ilog2",
839                body: "a.checked_ilog2()",
840                ret: "u32",
841                ..partial_unop.clone()
842            },
843            NumericOp {
844                name: "ilog2",
845                body: r#"a.checked_ilog2().unwrap_or_else(|| panic!("ilog2 overflow: {a}"))"#,
846                ret: "u32",
847                ..unop.clone()
848            },
849            NumericOp {
850                name: "trailing_zeros",
851                body: "a.trailing_zeros()",
852                ret: "u32",
853                ..unop.clone()
854            },
855            NumericOp {
856                name: "trailing_ones",
857                body: "a.trailing_ones()",
858                ret: "u32",
859                ..unop.clone()
860            },
861            NumericOp {
862                name: "leading_zeros",
863                body: "a.leading_zeros()",
864                ret: "u32",
865                ..unop.clone()
866            },
867            NumericOp {
868                name: "leading_ones",
869                body: "a.leading_ones()",
870                ret: "u32",
871                ..unop.clone()
872            },
873        ];
874
875        // Operations that apply only to signed numbers.
876        let signed_ops = [
877            NumericOp {
878                name: "checked_neg",
879                body: "a.checked_neg()",
880                ..partial_unop.clone()
881            },
882            NumericOp {
883                name: "wrapping_neg",
884                body: "a.wrapping_neg()",
885                ..unop.clone()
886            },
887            NumericOp {
888                name: "neg",
889                body: r#"a.checked_neg().unwrap_or_else(|| panic!("negation overflow: {a}"))"#,
890                ..unop.clone()
891            },
892        ];
893
894        // Operations that apply only to unsigned numbers.
895        let unsigned_ops = [NumericOp {
896            name: "is_power_of_two",
897            body: "a.is_power_of_two()",
898            ..predicate.clone()
899        }];
900
901        struct IterIf<I> {
902            condition: bool,
903            iter: I,
904        }
905
906        impl<I: Iterator> Iterator for IterIf<I> {
907            type Item = I::Item;
908
909            fn next(&mut self) -> Option<Self::Item> {
910                if self.condition {
911                    self.iter.next()
912                } else {
913                    None
914                }
915            }
916        }
917
918        ops.into_iter()
919            .chain(IterIf {
920                condition: ty.signed,
921                iter: signed_ops.into_iter(),
922            })
923            .chain(IterIf {
924                condition: !ty.signed,
925                iter: unsigned_ops.into_iter(),
926            })
927    }
928}
929
930fn gen_numerics_isle(isle: &mut Formatter, rust: &mut Formatter) {
931    fmtln!(rust, "#[macro_export]");
932    fmtln!(rust, "#[doc(hidden)]");
933    fmtln!(rust, "macro_rules! isle_numerics_methods {{");
934    rust.indent_push();
935    fmtln!(rust, "() => {{");
936    rust.indent_push();
937
938    for ty in NumericType::all() {
939        for op in NumericOp::ops_for_type(&ty) {
940            let ty = ty.name();
941            let op_name = format!("{ty}_{}", op.name);
942            let partial = if op.partial { " partial" } else { "" };
943            let ret = op.ret;
944            fmtln!(isle, "(decl pure{partial} {op_name} (");
945            isle.indent(|isle| {
946                for (_arg_name, arg_ty) in op.args.iter() {
947                    fmtln!(isle, "{arg_ty}");
948                }
949            });
950            fmtln!(isle, ") {ret})");
951            fmtln!(isle, "(extern constructor {op_name} {op_name})");
952
953            let ret = if op.partial {
954                Cow::from(format!("Option<{ret}>"))
955            } else {
956                Cow::from(ret)
957            };
958            let body = op.body;
959            fmtln!(rust, "#[inline]");
960            fmtln!(rust, "fn {op_name}(");
961            rust.indent(|rust| {
962                fmtln!(rust, "&mut self,");
963                for (arg_name, arg_ty) in op.args.iter() {
964                    fmtln!(rust, "{arg_name}: {arg_ty},");
965                }
966            });
967            fmtln!(rust, ") -> {ret} {{");
968            rust.indent(|rust| {
969                fmtln!(rust, "{body}");
970            });
971            fmtln!(rust, "}}");
972
973            // When generating extractors for a `{ty}_is_foo` predicate,
974            // we generate the following:
975            //
976            // * bool <- ty etor: `{ty}_matches_foo`
977            // * ty <- ty etor: `{ty}_extract_foo`
978            // * () <- ty etor: `{ty}_when_foo`
979            // * () <- ty etor: `{ty}_when_not_foo`
980            //
981            // The last three are defined as local extractors that are
982            // implemented in terms of the first. This gives the ISLE compiler
983            // visibility into the extractors' overlapping-ness.
984            if op.etors {
985                debug_assert_eq!(op.args.len(), 1);
986                debug_assert_eq!(op.args[0].1, ty);
987                debug_assert_eq!(op.ret, "bool");
988                debug_assert!(op.name.starts_with("is_"));
989
990                // Cut of the `is_` prefix.
991                let base_name = &op.name[3..];
992                debug_assert!(base_name.len() > 0);
993
994                fmtln!(isle, "(decl pure {ty}_matches_{base_name} (bool) {ty})");
995                fmtln!(
996                    isle,
997                    "(extern extractor {ty}_matches_{base_name} {ty}_matches_{base_name})"
998                );
999                fmtln!(rust, "#[inline]");
1000                fmtln!(
1001                    rust,
1002                    "fn {ty}_matches_{base_name}(&mut self, a: {ty}) -> Option<bool> {{"
1003                );
1004                rust.indent(|rust| {
1005                    fmtln!(rust, "Some({body})");
1006                });
1007                fmtln!(rust, "}}");
1008
1009                fmtln!(isle, "(decl pure {ty}_extract_{base_name} ({ty}) {ty})");
1010                fmtln!(
1011                    isle,
1012                    "(extractor ({ty}_extract_{base_name} x) (and ({ty}_matches_{base_name} true) x))"
1013                );
1014
1015                fmtln!(isle, "(decl pure {ty}_when_{base_name} () {ty})");
1016                fmtln!(
1017                    isle,
1018                    "(extractor ({ty}_when_{base_name}) ({ty}_matches_{base_name} true))"
1019                );
1020
1021                fmtln!(isle, "(decl pure {ty}_when_not_{base_name} () {ty})");
1022                fmtln!(
1023                    isle,
1024                    "(extractor ({ty}_when_not_{base_name}) ({ty}_matches_{base_name} false))"
1025                );
1026            }
1027
1028            isle.empty_line();
1029            rust.empty_line();
1030        }
1031    }
1032
1033    // Numeric type conversions.
1034    //
1035    // Naming and conventions:
1036    //
1037    // * Constructors:
1038    //   * "<from>_into_<to>" for lossless, infallible conversion
1039    //   * "<from>_try_into_<to>" for lossless, fallible conversions (exposed as
1040    //     partial constructors)
1041    //   * "<from>_unwrap_into_<to>" for lossless, fallible conversions that will
1042    //     panic at runtime if the conversion would be lossy
1043    //   * "<from>_truncate_into_<to>" for lossy, infallible conversions that
1044    //     ignore upper bits
1045    //   * "<from>_cast_[un]signed" for signed-to-unsigned (and vice versa)
1046    //     reinterpretation
1047    // * Extractors:
1048    //   * "<to>_from_<from>" for both fallible and infallible extractors
1049    //   * No unwrapping extractors
1050    //   * No truncating extractors
1051    //   * No signed-to-unsigned reinterpreting extractors
1052    for from in NumericType::all() {
1053        for to in NumericType::all() {
1054            if from == to {
1055                continue;
1056            }
1057
1058            let from_name = from.name();
1059            let to_name = to.name();
1060
1061            let lossy = match (from.byte_width.cmp(&to.byte_width), from.signed, to.signed) {
1062                // Widening with the same signedness is lossless.
1063                (Ordering::Less, true, true) | (Ordering::Less, false, false) => false,
1064                // Widening from unsigned to signed is lossless.
1065                (Ordering::Less, false, true) => false,
1066                // Widening from signed to unsigned is lossy.
1067                (Ordering::Less, true, false) => true,
1068                // Same width means we must be changing sign, since we skip
1069                // `from == to`, and this is lossy.
1070                (Ordering::Equal, _, _) => {
1071                    debug_assert_ne!(from.signed, to.signed);
1072                    true
1073                }
1074                // Narrowing is always lossy.
1075                (Ordering::Greater, _, _) => true,
1076            };
1077
1078            let (ctor, partial, rust_ret) = if lossy {
1079                (
1080                    "try_into",
1081                    " partial",
1082                    Cow::from(format!("Option<{to_name}>")),
1083                )
1084            } else {
1085                ("into", "", Cow::from(to_name))
1086            };
1087
1088            // Constructor.
1089            fmtln!(
1090                isle,
1091                "(decl pure{partial} {from_name}_{ctor}_{to_name} ({from_name}) {to_name})"
1092            );
1093            fmtln!(
1094                isle,
1095                "(extern constructor {from_name}_{ctor}_{to_name} {from_name}_{ctor}_{to_name})"
1096            );
1097            if !lossy {
1098                fmtln!(
1099                    isle,
1100                    "(convert {from_name} {to_name} {from_name}_{ctor}_{to_name})"
1101                );
1102            }
1103            fmtln!(rust, "#[inline]");
1104            fmtln!(
1105                rust,
1106                "fn {from_name}_{ctor}_{to_name}(&mut self, x: {from_name}) -> {rust_ret} {{"
1107            );
1108            rust.indent(|rust| {
1109                if lossy {
1110                    fmtln!(rust, "{to_name}::try_from(x).ok()");
1111                } else {
1112                    fmtln!(rust, "{to_name}::from(x)");
1113                }
1114            });
1115            fmtln!(rust, "}}");
1116
1117            // Unwrapping constructor.
1118            if lossy {
1119                fmtln!(
1120                    isle,
1121                    "(decl pure {from_name}_unwrap_into_{to_name} ({from_name}) {to_name})"
1122                );
1123                fmtln!(
1124                    isle,
1125                    "(extern constructor {from_name}_unwrap_into_{to_name} {from_name}_unwrap_into_{to_name})"
1126                );
1127                fmtln!(rust, "#[inline]");
1128                fmtln!(
1129                    rust,
1130                    "fn {from_name}_unwrap_into_{to_name}(&mut self, x: {from_name}) -> {to_name} {{"
1131                );
1132                rust.indent(|rust| {
1133                    fmtln!(rust, "{to_name}::try_from(x).unwrap()");
1134                });
1135                fmtln!(rust, "}}");
1136            }
1137
1138            // Truncating constructor.
1139            if lossy && from.signed == to.signed {
1140                fmtln!(
1141                    isle,
1142                    "(decl pure {from_name}_truncate_into_{to_name} ({from_name}) {to_name})"
1143                );
1144                fmtln!(
1145                    isle,
1146                    "(extern constructor {from_name}_truncate_into_{to_name} {from_name}_truncate_into_{to_name})"
1147                );
1148                fmtln!(rust, "#[inline]");
1149                fmtln!(
1150                    rust,
1151                    "fn {from_name}_truncate_into_{to_name}(&mut self, x: {from_name}) -> {to_name} {{"
1152                );
1153                rust.indent(|rust| {
1154                    fmtln!(rust, "x as {to_name}");
1155                });
1156                fmtln!(rust, "}}");
1157            }
1158
1159            // Signed-to-unsigned reinterpreting constructor.
1160            if from.byte_width == to.byte_width {
1161                debug_assert_ne!(from.signed, to.signed);
1162                let cast_name = if to.signed {
1163                    "cast_signed"
1164                } else {
1165                    "cast_unsigned"
1166                };
1167                fmtln!(
1168                    isle,
1169                    "(decl pure {from_name}_{cast_name} ({from_name}) {to_name})"
1170                );
1171                fmtln!(
1172                    isle,
1173                    "(extern constructor {from_name}_{cast_name} {from_name}_{cast_name})"
1174                );
1175                fmtln!(rust, "#[inline]");
1176                fmtln!(
1177                    rust,
1178                    "fn {from_name}_{cast_name}(&mut self, x: {from_name}) -> {to_name} {{"
1179                );
1180                rust.indent(|rust| {
1181                    // TODO: Once our MSRV is >= 1.87, we should use
1182                    // `x.cast_[un]signed()` here.
1183                    fmtln!(rust, "x as {to_name}");
1184                });
1185                fmtln!(rust, "}}");
1186            }
1187
1188            // Extractor.
1189            fmtln!(
1190                isle,
1191                "(decl pure {to_name}_from_{from_name} ({to_name}) {from_name})"
1192            );
1193            fmtln!(
1194                isle,
1195                "(extern extractor {to_name}_from_{from_name} {from_name}_from_{to_name})"
1196            );
1197            fmtln!(rust, "#[inline]");
1198            fmtln!(
1199                rust,
1200                "fn {from_name}_from_{to_name}(&mut self, x: {from_name}) -> Option<{to_name}> {{"
1201            );
1202            rust.indent(|rust| {
1203                if lossy {
1204                    fmtln!(rust, "x.try_into().ok()");
1205                } else {
1206                    fmtln!(rust, "Some(x.into())");
1207                }
1208            });
1209            fmtln!(rust, "}}");
1210
1211            isle.empty_line();
1212            rust.empty_line();
1213        }
1214    }
1215
1216    rust.indent_pop();
1217    fmtln!(rust, "}}");
1218    rust.indent_pop();
1219    fmtln!(rust, "}}");
1220}
1221
1222pub(crate) fn generate(
1223    formats: &[Rc<InstructionFormat>],
1224    all_inst: &AllInstructions,
1225    isle_numerics_filename: &str,
1226    rust_numerics_filename: &str,
1227    isle_opt_filename: &str,
1228    isle_lower_filename: &str,
1229    isle_dir: &std::path::Path,
1230) -> Result<(), error::Error> {
1231    // Numerics
1232    let mut isle_fmt = Formatter::new(Language::Isle);
1233    let mut rust_fmt = Formatter::new(Language::Rust);
1234    gen_numerics_isle(&mut isle_fmt, &mut rust_fmt);
1235    isle_fmt.write(isle_numerics_filename, isle_dir)?;
1236    rust_fmt.write(rust_numerics_filename, isle_dir)?;
1237
1238    // ISLE DSL: mid-end ("opt") generated bindings.
1239    let mut fmt = Formatter::new(Language::Isle);
1240    gen_opt_isle(&formats, all_inst, &mut fmt);
1241    fmt.write(isle_opt_filename, isle_dir)?;
1242
1243    // ISLE DSL: lowering generated bindings.
1244    let mut fmt = Formatter::new(Language::Isle);
1245    gen_lower_isle(&formats, all_inst, &mut fmt);
1246    fmt.write(isle_lower_filename, isle_dir)?;
1247
1248    Ok(())
1249}