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    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
498#[derive(Clone, Copy, PartialEq, Eq)]
499struct NumericType {
500    signed: bool,
501    byte_width: u8,
502}
503
504impl NumericType {
505    fn all() -> impl Iterator<Item = NumericType> {
506        [1, 2, 4, 8, 16].into_iter().flat_map(|byte_width| {
507            [true, false]
508                .into_iter()
509                .map(move |signed| NumericType { signed, byte_width })
510        })
511    }
512
513    fn name(&self) -> &'static str {
514        let idx = self.byte_width.ilog2();
515        let idx = usize::try_from(idx).unwrap();
516        if self.signed {
517            ["i8", "i16", "i32", "i64", "i128"][idx]
518        } else {
519            ["u8", "u16", "u32", "u64", "u128"][idx]
520        }
521    }
522}
523
524#[derive(Clone, Default, PartialEq, Eq)]
525struct NumericOp<'a> {
526    /// The name of this operation.
527    name: &'a str,
528    /// The return type of this operation.
529    ret: &'a str,
530    /// Whether this operation is partial.
531    partial: bool,
532    /// (name, type) pairs of arguments.
533    args: Rc<[(&'a str, &'a str)]>,
534    /// The source text for the constructor's body.
535    body: &'a str,
536    /// Whether extractors should be generated for this op.
537    ///
538    /// Must have `arity == 1`, `ret == bool`, and `name.starts_with("is_")`.
539    etors: bool,
540}
541
542impl NumericOp<'_> {
543    fn ops_for_type(ty: &NumericType) -> impl Iterator<Item = NumericOp<'_>> {
544        let arity1 = NumericOp {
545            args: [("a", ty.name())].into(),
546            ..NumericOp::default()
547        };
548
549        let arity2 = NumericOp {
550            args: [("a", ty.name()), ("b", ty.name())].into(),
551            ..NumericOp::default()
552        };
553
554        let comparison = NumericOp {
555            ret: "bool",
556            ..arity2.clone()
557        };
558
559        let predicate = NumericOp {
560            ret: "bool",
561            etors: true,
562            ..arity1.clone()
563        };
564
565        let binop = NumericOp {
566            ret: ty.name(),
567            ..arity2.clone()
568        };
569
570        let partial_binop = NumericOp {
571            ret: ty.name(),
572            partial: true,
573            ..binop.clone()
574        };
575
576        let unop = NumericOp {
577            ret: ty.name(),
578            ..arity1.clone()
579        };
580
581        let partial_unop = NumericOp {
582            ret: ty.name(),
583            partial: true,
584            ..unop.clone()
585        };
586
587        let shift = NumericOp {
588            args: [("a", ty.name()), ("b", "u32")].into(),
589            ..binop.clone()
590        };
591
592        let partial_shift = NumericOp {
593            args: [("a", ty.name()), ("b", "u32")].into(),
594            ..partial_binop.clone()
595        };
596
597        // Operations that apply to both signed and unsigned numbers.
598        let ops = [
599            // Comparisons.
600            NumericOp {
601                name: "eq",
602                body: "a == b",
603                ..comparison.clone()
604            },
605            NumericOp {
606                name: "ne",
607                body: "a != b",
608                ..comparison.clone()
609            },
610            NumericOp {
611                name: "lt",
612                body: "a < b",
613                ..comparison.clone()
614            },
615            NumericOp {
616                name: "lt_eq",
617                body: "a <= b",
618                ..comparison.clone()
619            },
620            NumericOp {
621                name: "gt",
622                body: "a > b",
623                ..comparison.clone()
624            },
625            NumericOp {
626                name: "gt_eq",
627                body: "a >= b",
628                ..comparison.clone()
629            },
630            // Arithmetic operations.
631            //
632            // For each operation (e.g. addition) we have three variants:
633            //
634            // * partial ctor `checked_add`: no return value on overflow
635            // * ctor `wrapping_add`: wraps on overflow
636            // * ctor `add`: non-partial but panics at runtime on overflow
637            NumericOp {
638                name: "checked_add",
639                body: "a.checked_add(b)",
640                ..partial_binop.clone()
641            },
642            NumericOp {
643                name: "wrapping_add",
644                body: "a.wrapping_add(b)",
645                ..binop.clone()
646            },
647            NumericOp {
648                name: "add",
649                body: r#"a.checked_add(b).unwrap_or_else(|| panic!("addition overflow: {a} + {b}"))"#,
650                ..binop.clone()
651            },
652            NumericOp {
653                name: "checked_sub",
654                body: "a.checked_sub(b)",
655                ..partial_binop.clone()
656            },
657            NumericOp {
658                name: "wrapping_sub",
659                body: "a.wrapping_sub(b)",
660                ..binop.clone()
661            },
662            NumericOp {
663                name: "sub",
664                body: r#"a.checked_sub(b).unwrap_or_else(|| panic!("subtraction overflow: {a} - {b}"))"#,
665                ..binop.clone()
666            },
667            NumericOp {
668                name: "checked_mul",
669                body: "a.checked_mul(b)",
670                ..partial_binop.clone()
671            },
672            NumericOp {
673                name: "wrapping_mul",
674                body: "a.wrapping_mul(b)",
675                ..binop.clone()
676            },
677            NumericOp {
678                name: "mul",
679                body: r#"a.checked_mul(b).unwrap_or_else(|| panic!("multiplication overflow: {a} * {b}"))"#,
680                ..binop.clone()
681            },
682            NumericOp {
683                name: "checked_div",
684                body: "a.checked_div(b)",
685                ..partial_binop.clone()
686            },
687            NumericOp {
688                name: "wrapping_div",
689                body: "a.wrapping_div(b)",
690                ..binop.clone()
691            },
692            NumericOp {
693                name: "div",
694                body: r#"a.checked_div(b).unwrap_or_else(|| panic!("div failure: {a} / {b}"))"#,
695                ..binop.clone()
696            },
697            NumericOp {
698                name: "checked_rem",
699                body: "a.checked_rem(b)",
700                ..partial_binop.clone()
701            },
702            NumericOp {
703                name: "rem",
704                body: r#"a.checked_rem(b).unwrap_or_else(|| panic!("rem failure: {a} % {b}"))"#,
705                ..binop.clone()
706            },
707            // Bitwise operations.
708            //
709            // When applicable (e.g. shifts) we have checked, wrapping, and
710            // unwrapping variants, similar to arithmetic operations.
711            NumericOp {
712                name: "and",
713                body: "a & b",
714                ..binop.clone()
715            },
716            NumericOp {
717                name: "or",
718                body: "a | b",
719                ..binop.clone()
720            },
721            NumericOp {
722                name: "xor",
723                body: "a ^ b",
724                ..binop.clone()
725            },
726            NumericOp {
727                name: "not",
728                body: "!a",
729                ..unop.clone()
730            },
731            NumericOp {
732                name: "checked_shl",
733                body: "a.checked_shl(b)",
734                ..partial_shift.clone()
735            },
736            NumericOp {
737                name: "wrapping_shl",
738                body: "a.wrapping_shl(b)",
739                ..shift.clone()
740            },
741            NumericOp {
742                name: "shl",
743                body: r#"a.checked_shl(b).unwrap_or_else(|| panic!("shl overflow: {a} << {b}"))"#,
744                ..shift.clone()
745            },
746            NumericOp {
747                name: "checked_shr",
748                body: "a.checked_shr(b)",
749                ..partial_shift.clone()
750            },
751            NumericOp {
752                name: "wrapping_shr",
753                body: "a.wrapping_shr(b)",
754                ..shift.clone()
755            },
756            NumericOp {
757                name: "shr",
758                body: r#"a.checked_shr(b).unwrap_or_else(|| panic!("shr overflow: {a} >> {b}"))"#,
759                ..shift.clone()
760            },
761            // Predicates.
762            //
763            // We generate both pure constructors and a variety of extractors
764            // for these. See the relevant comments in `gen_numerics_isle` about
765            // the extractors.
766            NumericOp {
767                name: "is_zero",
768                body: "a == 0",
769                ..predicate.clone()
770            },
771            NumericOp {
772                name: "is_non_zero",
773                body: "a != 0",
774                ..predicate.clone()
775            },
776            NumericOp {
777                name: "is_odd",
778                body: "a & 1 == 1",
779                ..predicate.clone()
780            },
781            NumericOp {
782                name: "is_even",
783                body: "a & 1 == 0",
784                ..predicate.clone()
785            },
786            // Miscellaneous unary operations.
787            NumericOp {
788                name: "checked_ilog2",
789                body: "a.checked_ilog2()",
790                ret: "u32",
791                ..partial_unop.clone()
792            },
793            NumericOp {
794                name: "ilog2",
795                body: r#"a.checked_ilog2().unwrap_or_else(|| panic!("ilog2 overflow: {a}"))"#,
796                ret: "u32",
797                ..unop.clone()
798            },
799            NumericOp {
800                name: "trailing_zeros",
801                body: "a.trailing_zeros()",
802                ret: "u32",
803                ..unop.clone()
804            },
805            NumericOp {
806                name: "trailing_ones",
807                body: "a.trailing_ones()",
808                ret: "u32",
809                ..unop.clone()
810            },
811            NumericOp {
812                name: "leading_zeros",
813                body: "a.leading_zeros()",
814                ret: "u32",
815                ..unop.clone()
816            },
817            NumericOp {
818                name: "leading_ones",
819                body: "a.leading_ones()",
820                ret: "u32",
821                ..unop.clone()
822            },
823        ];
824
825        // Operations that apply only to signed numbers.
826        let signed_ops = [
827            NumericOp {
828                name: "checked_neg",
829                body: "a.checked_neg()",
830                ..partial_unop.clone()
831            },
832            NumericOp {
833                name: "wrapping_neg",
834                body: "a.wrapping_neg()",
835                ..unop.clone()
836            },
837            NumericOp {
838                name: "neg",
839                body: r#"a.checked_neg().unwrap_or_else(|| panic!("negation overflow: {a}"))"#,
840                ..unop.clone()
841            },
842        ];
843
844        // Operations that apply only to unsigned numbers.
845        let unsigned_ops = [NumericOp {
846            name: "is_power_of_two",
847            body: "a.is_power_of_two()",
848            ..predicate.clone()
849        }];
850
851        struct IterIf<I> {
852            condition: bool,
853            iter: I,
854        }
855
856        impl<I: Iterator> Iterator for IterIf<I> {
857            type Item = I::Item;
858
859            fn next(&mut self) -> Option<Self::Item> {
860                if self.condition {
861                    self.iter.next()
862                } else {
863                    None
864                }
865            }
866        }
867
868        ops.into_iter()
869            .chain(IterIf {
870                condition: ty.signed,
871                iter: signed_ops.into_iter(),
872            })
873            .chain(IterIf {
874                condition: !ty.signed,
875                iter: unsigned_ops.into_iter(),
876            })
877    }
878}
879
880fn gen_numerics_isle(isle: &mut Formatter, rust: &mut Formatter) {
881    fmtln!(rust, "#[macro_export]");
882    fmtln!(rust, "#[doc(hidden)]");
883    fmtln!(rust, "macro_rules! isle_numerics_methods {{");
884    rust.indent_push();
885    fmtln!(rust, "() => {{");
886    rust.indent_push();
887
888    for ty in NumericType::all() {
889        for op in NumericOp::ops_for_type(&ty) {
890            let ty = ty.name();
891            let op_name = format!("{ty}_{}", op.name);
892            let partial = if op.partial { " partial" } else { "" };
893            let ret = op.ret;
894            fmtln!(isle, "(decl pure{partial} {op_name} (");
895            isle.indent(|isle| {
896                for (_arg_name, arg_ty) in op.args.iter() {
897                    fmtln!(isle, "{arg_ty}");
898                }
899            });
900            fmtln!(isle, ") {ret})");
901            fmtln!(isle, "(extern constructor {op_name} {op_name})");
902
903            let ret = if op.partial {
904                Cow::from(format!("Option<{ret}>"))
905            } else {
906                Cow::from(ret)
907            };
908            let body = op.body;
909            fmtln!(rust, "#[inline]");
910            fmtln!(rust, "fn {op_name}(");
911            rust.indent(|rust| {
912                fmtln!(rust, "&mut self,");
913                for (arg_name, arg_ty) in op.args.iter() {
914                    fmtln!(rust, "{arg_name}: {arg_ty},");
915                }
916            });
917            fmtln!(rust, ") -> {ret} {{");
918            rust.indent(|rust| {
919                fmtln!(rust, "{body}");
920            });
921            fmtln!(rust, "}}");
922
923            // When generating extractors for a `{ty}_is_foo` predicate,
924            // we generate the following:
925            //
926            // * bool <- ty etor: `{ty}_matches_foo`
927            // * ty <- ty etor: `{ty}_extract_foo`
928            // * () <- ty etor: `{ty}_when_foo`
929            // * () <- ty etor: `{ty}_when_not_foo`
930            //
931            // The last three are defined as local extractors that are
932            // implemented in terms of the first. This gives the ISLE compiler
933            // visibility into the extractors' overlapping-ness.
934            if op.etors {
935                debug_assert_eq!(op.args.len(), 1);
936                debug_assert_eq!(op.args[0].1, ty);
937                debug_assert_eq!(op.ret, "bool");
938                debug_assert!(op.name.starts_with("is_"));
939
940                // Cut of the `is_` prefix.
941                let base_name = &op.name[3..];
942                debug_assert!(base_name.len() > 0);
943
944                fmtln!(isle, "(decl pure {ty}_matches_{base_name} (bool) {ty})");
945                fmtln!(
946                    isle,
947                    "(extern extractor {ty}_matches_{base_name} {ty}_matches_{base_name})"
948                );
949                fmtln!(rust, "#[inline]");
950                fmtln!(
951                    rust,
952                    "fn {ty}_matches_{base_name}(&mut self, a: {ty}) -> Option<bool> {{"
953                );
954                rust.indent(|rust| {
955                    fmtln!(rust, "Some({body})");
956                });
957                fmtln!(rust, "}}");
958
959                fmtln!(isle, "(decl pure {ty}_extract_{base_name} ({ty}) {ty})");
960                fmtln!(
961                    isle,
962                    "(extractor ({ty}_extract_{base_name} x) (and ({ty}_matches_{base_name} true) x))"
963                );
964
965                fmtln!(isle, "(decl pure {ty}_when_{base_name} () {ty})");
966                fmtln!(
967                    isle,
968                    "(extractor ({ty}_when_{base_name}) ({ty}_matches_{base_name} true))"
969                );
970
971                fmtln!(isle, "(decl pure {ty}_when_not_{base_name} () {ty})");
972                fmtln!(
973                    isle,
974                    "(extractor ({ty}_when_not_{base_name}) ({ty}_matches_{base_name} false))"
975                );
976            }
977
978            isle.empty_line();
979            rust.empty_line();
980        }
981    }
982
983    // Numeric type conversions.
984    //
985    // Naming and conventions:
986    //
987    // * Constructors:
988    //   * "<from>_into_<to>" for lossless, infallible conversion
989    //   * "<from>_try_into_<to>" for lossless, fallible conversions (exposed as
990    //     partial constructors)
991    //   * "<from>_unwrap_into_<to>" for lossless, fallible conversions that will
992    //     panic at runtime if the conversion would be lossy
993    //   * "<from>_truncate_into_<to>" for lossy, infallible conversions that
994    //     ignore upper bits
995    //   * "<from>_cast_[un]signed" for signed-to-unsigned (and vice versa)
996    //     reinterpretation
997    // * Extractors:
998    //   * "<to>_from_<from>" for both fallible and infallible extractors
999    //   * No unwrapping extractors
1000    //   * No truncating extractors
1001    //   * No signed-to-unsigned reinterpreting extractors
1002    for from in NumericType::all() {
1003        for to in NumericType::all() {
1004            if from == to {
1005                continue;
1006            }
1007
1008            let from_name = from.name();
1009            let to_name = to.name();
1010
1011            let lossy = match (from.byte_width.cmp(&to.byte_width), from.signed, to.signed) {
1012                // Widening with the same signedness is lossless.
1013                (Ordering::Less, true, true) | (Ordering::Less, false, false) => false,
1014                // Widening from unsigned to signed is lossless.
1015                (Ordering::Less, false, true) => false,
1016                // Widening from signed to unsigned is lossy.
1017                (Ordering::Less, true, false) => true,
1018                // Same width means we must be changing sign, since we skip
1019                // `from == to`, and this is lossy.
1020                (Ordering::Equal, _, _) => {
1021                    debug_assert_ne!(from.signed, to.signed);
1022                    true
1023                }
1024                // Narrowing is always lossy.
1025                (Ordering::Greater, _, _) => true,
1026            };
1027
1028            let (ctor, partial, rust_ret) = if lossy {
1029                (
1030                    "try_into",
1031                    " partial",
1032                    Cow::from(format!("Option<{to_name}>")),
1033                )
1034            } else {
1035                ("into", "", Cow::from(to_name))
1036            };
1037
1038            // Constructor.
1039            fmtln!(
1040                isle,
1041                "(decl pure{partial} {from_name}_{ctor}_{to_name} ({from_name}) {to_name})"
1042            );
1043            fmtln!(
1044                isle,
1045                "(extern constructor {from_name}_{ctor}_{to_name} {from_name}_{ctor}_{to_name})"
1046            );
1047            if !lossy {
1048                fmtln!(
1049                    isle,
1050                    "(convert {from_name} {to_name} {from_name}_{ctor}_{to_name})"
1051                );
1052            }
1053            fmtln!(rust, "#[inline]");
1054            fmtln!(
1055                rust,
1056                "fn {from_name}_{ctor}_{to_name}(&mut self, x: {from_name}) -> {rust_ret} {{"
1057            );
1058            rust.indent(|rust| {
1059                if lossy {
1060                    fmtln!(rust, "{to_name}::try_from(x).ok()");
1061                } else {
1062                    fmtln!(rust, "{to_name}::from(x)");
1063                }
1064            });
1065            fmtln!(rust, "}}");
1066
1067            // Unwrapping constructor.
1068            if lossy {
1069                fmtln!(
1070                    isle,
1071                    "(decl pure {from_name}_unwrap_into_{to_name} ({from_name}) {to_name})"
1072                );
1073                fmtln!(
1074                    isle,
1075                    "(extern constructor {from_name}_unwrap_into_{to_name} {from_name}_unwrap_into_{to_name})"
1076                );
1077                fmtln!(rust, "#[inline]");
1078                fmtln!(
1079                    rust,
1080                    "fn {from_name}_unwrap_into_{to_name}(&mut self, x: {from_name}) -> {to_name} {{"
1081                );
1082                rust.indent(|rust| {
1083                    fmtln!(rust, "{to_name}::try_from(x).unwrap()");
1084                });
1085                fmtln!(rust, "}}");
1086            }
1087
1088            // Truncating constructor.
1089            if lossy && from.signed == to.signed {
1090                fmtln!(
1091                    isle,
1092                    "(decl pure {from_name}_truncate_into_{to_name} ({from_name}) {to_name})"
1093                );
1094                fmtln!(
1095                    isle,
1096                    "(extern constructor {from_name}_truncate_into_{to_name} {from_name}_truncate_into_{to_name})"
1097                );
1098                fmtln!(rust, "#[inline]");
1099                fmtln!(
1100                    rust,
1101                    "fn {from_name}_truncate_into_{to_name}(&mut self, x: {from_name}) -> {to_name} {{"
1102                );
1103                rust.indent(|rust| {
1104                    fmtln!(rust, "x as {to_name}");
1105                });
1106                fmtln!(rust, "}}");
1107            }
1108
1109            // Signed-to-unsigned reinterpreting constructor.
1110            if from.byte_width == to.byte_width {
1111                debug_assert_ne!(from.signed, to.signed);
1112                let cast_name = if to.signed {
1113                    "cast_signed"
1114                } else {
1115                    "cast_unsigned"
1116                };
1117                fmtln!(
1118                    isle,
1119                    "(decl pure {from_name}_{cast_name} ({from_name}) {to_name})"
1120                );
1121                fmtln!(
1122                    isle,
1123                    "(extern constructor {from_name}_{cast_name} {from_name}_{cast_name})"
1124                );
1125                fmtln!(rust, "#[inline]");
1126                fmtln!(
1127                    rust,
1128                    "fn {from_name}_{cast_name}(&mut self, x: {from_name}) -> {to_name} {{"
1129                );
1130                rust.indent(|rust| {
1131                    // TODO: Once our MSRV is >= 1.87, we should use
1132                    // `x.cast_[un]signed()` here.
1133                    fmtln!(rust, "x as {to_name}");
1134                });
1135                fmtln!(rust, "}}");
1136            }
1137
1138            // Extractor.
1139            fmtln!(
1140                isle,
1141                "(decl pure {to_name}_from_{from_name} ({to_name}) {from_name})"
1142            );
1143            fmtln!(
1144                isle,
1145                "(extern extractor {to_name}_from_{from_name} {from_name}_from_{to_name})"
1146            );
1147            fmtln!(rust, "#[inline]");
1148            fmtln!(
1149                rust,
1150                "fn {from_name}_from_{to_name}(&mut self, x: {from_name}) -> Option<{to_name}> {{"
1151            );
1152            rust.indent(|rust| {
1153                if lossy {
1154                    fmtln!(rust, "x.try_into().ok()");
1155                } else {
1156                    fmtln!(rust, "Some(x.into())");
1157                }
1158            });
1159            fmtln!(rust, "}}");
1160
1161            isle.empty_line();
1162            rust.empty_line();
1163        }
1164    }
1165
1166    rust.indent_pop();
1167    fmtln!(rust, "}}");
1168    rust.indent_pop();
1169    fmtln!(rust, "}}");
1170}
1171
1172pub(crate) fn generate(
1173    formats: &[Rc<InstructionFormat>],
1174    all_inst: &AllInstructions,
1175    isle_numerics_filename: &str,
1176    rust_numerics_filename: &str,
1177    isle_opt_filename: &str,
1178    isle_lower_filename: &str,
1179    isle_dir: &std::path::Path,
1180) -> Result<(), error::Error> {
1181    // Numerics
1182    let mut isle_fmt = Formatter::new(Language::Isle);
1183    let mut rust_fmt = Formatter::new(Language::Rust);
1184    gen_numerics_isle(&mut isle_fmt, &mut rust_fmt);
1185    isle_fmt.write(isle_numerics_filename, isle_dir)?;
1186    rust_fmt.write(rust_numerics_filename, isle_dir)?;
1187
1188    // ISLE DSL: mid-end ("opt") generated bindings.
1189    let mut fmt = Formatter::new(Language::Isle);
1190    gen_opt_isle(&formats, all_inst, &mut fmt);
1191    fmt.write(isle_opt_filename, isle_dir)?;
1192
1193    // ISLE DSL: lowering generated bindings.
1194    let mut fmt = Formatter::new(Language::Isle);
1195    gen_lower_isle(&formats, all_inst, &mut fmt);
1196    fmt.write(isle_lower_filename, isle_dir)?;
1197
1198    Ok(())
1199}