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            // Bitwise operations.
698            //
699            // When applicable (e.g. shifts) we have checked, wrapping, and
700            // unwrapping variants, similar to arithmetic operations.
701            NumericOp {
702                name: "and",
703                body: "a & b",
704                ..binop.clone()
705            },
706            NumericOp {
707                name: "or",
708                body: "a | b",
709                ..binop.clone()
710            },
711            NumericOp {
712                name: "xor",
713                body: "a ^ b",
714                ..binop.clone()
715            },
716            NumericOp {
717                name: "not",
718                body: "!a",
719                ..unop.clone()
720            },
721            NumericOp {
722                name: "checked_shl",
723                body: "a.checked_shl(b)",
724                ..partial_shift.clone()
725            },
726            NumericOp {
727                name: "wrapping_shl",
728                body: "a.wrapping_shl(b)",
729                ..shift.clone()
730            },
731            NumericOp {
732                name: "shl",
733                body: r#"a.checked_shl(b).unwrap_or_else(|| panic!("shl overflow: {a} << {b}"))"#,
734                ..shift.clone()
735            },
736            NumericOp {
737                name: "checked_shr",
738                body: "a.checked_shr(b)",
739                ..partial_shift.clone()
740            },
741            NumericOp {
742                name: "wrapping_shr",
743                body: "a.wrapping_shr(b)",
744                ..shift.clone()
745            },
746            NumericOp {
747                name: "shr",
748                body: r#"a.checked_shr(b).unwrap_or_else(|| panic!("shr overflow: {a} >> {b}"))"#,
749                ..shift.clone()
750            },
751            // Predicates.
752            //
753            // We generate both pure constructors and a variety of extractors
754            // for these. See the relevant comments in `gen_numerics_isle` about
755            // the extractors.
756            NumericOp {
757                name: "is_zero",
758                body: "a == 0",
759                ..predicate.clone()
760            },
761            NumericOp {
762                name: "is_non_zero",
763                body: "a != 0",
764                ..predicate.clone()
765            },
766            NumericOp {
767                name: "is_odd",
768                body: "a & 1 == 1",
769                ..predicate.clone()
770            },
771            NumericOp {
772                name: "is_even",
773                body: "a & 1 == 0",
774                ..predicate.clone()
775            },
776            // Miscellaneous unary operations.
777            NumericOp {
778                name: "checked_ilog2",
779                body: "a.checked_ilog2()",
780                ret: "u32",
781                ..partial_unop.clone()
782            },
783            NumericOp {
784                name: "ilog2",
785                body: r#"a.checked_ilog2().unwrap_or_else(|| panic!("ilog2 overflow: {a}"))"#,
786                ret: "u32",
787                ..unop.clone()
788            },
789            NumericOp {
790                name: "trailing_zeros",
791                body: "a.trailing_zeros()",
792                ret: "u32",
793                ..unop.clone()
794            },
795            NumericOp {
796                name: "trailing_ones",
797                body: "a.trailing_ones()",
798                ret: "u32",
799                ..unop.clone()
800            },
801            NumericOp {
802                name: "leading_zeros",
803                body: "a.leading_zeros()",
804                ret: "u32",
805                ..unop.clone()
806            },
807            NumericOp {
808                name: "leading_ones",
809                body: "a.leading_ones()",
810                ret: "u32",
811                ..unop.clone()
812            },
813        ];
814
815        // Operations that apply only to signed numbers.
816        let signed_ops = [
817            NumericOp {
818                name: "checked_neg",
819                body: "a.checked_neg()",
820                ..partial_unop.clone()
821            },
822            NumericOp {
823                name: "wrapping_neg",
824                body: "a.wrapping_neg()",
825                ..unop.clone()
826            },
827            NumericOp {
828                name: "neg",
829                body: r#"a.checked_neg().unwrap_or_else(|| panic!("negation overflow: {a}"))"#,
830                ..unop.clone()
831            },
832        ];
833
834        // Operations that apply only to unsigned numbers.
835        let unsigned_ops = [NumericOp {
836            name: "is_power_of_two",
837            body: "a.is_power_of_two()",
838            ..predicate.clone()
839        }];
840
841        struct IterIf<I> {
842            condition: bool,
843            iter: I,
844        }
845
846        impl<I: Iterator> Iterator for IterIf<I> {
847            type Item = I::Item;
848
849            fn next(&mut self) -> Option<Self::Item> {
850                if self.condition {
851                    self.iter.next()
852                } else {
853                    None
854                }
855            }
856        }
857
858        ops.into_iter()
859            .chain(IterIf {
860                condition: ty.signed,
861                iter: signed_ops.into_iter(),
862            })
863            .chain(IterIf {
864                condition: !ty.signed,
865                iter: unsigned_ops.into_iter(),
866            })
867    }
868}
869
870fn gen_numerics_isle(isle: &mut Formatter, rust: &mut Formatter) {
871    fmtln!(rust, "#[macro_export]");
872    fmtln!(rust, "#[doc(hidden)]");
873    fmtln!(rust, "macro_rules! isle_numerics_methods {{");
874    rust.indent_push();
875    fmtln!(rust, "() => {{");
876    rust.indent_push();
877
878    for ty in NumericType::all() {
879        for op in NumericOp::ops_for_type(&ty) {
880            let ty = ty.name();
881            let op_name = format!("{ty}_{}", op.name);
882            let partial = if op.partial { " partial" } else { "" };
883            let ret = op.ret;
884            fmtln!(isle, "(decl pure{partial} {op_name} (");
885            isle.indent(|isle| {
886                for (_arg_name, arg_ty) in op.args.iter() {
887                    fmtln!(isle, "{arg_ty}");
888                }
889            });
890            fmtln!(isle, ") {ret})");
891            fmtln!(isle, "(extern constructor {op_name} {op_name})");
892
893            let ret = if op.partial {
894                Cow::from(format!("Option<{ret}>"))
895            } else {
896                Cow::from(ret)
897            };
898            let body = op.body;
899            fmtln!(rust, "#[inline]");
900            fmtln!(rust, "fn {op_name}(");
901            rust.indent(|rust| {
902                fmtln!(rust, "&mut self,");
903                for (arg_name, arg_ty) in op.args.iter() {
904                    fmtln!(rust, "{arg_name}: {arg_ty},");
905                }
906            });
907            fmtln!(rust, ") -> {ret} {{");
908            rust.indent(|rust| {
909                fmtln!(rust, "{body}");
910            });
911            fmtln!(rust, "}}");
912
913            // When generating extractors for a `{ty}_is_foo` predicate,
914            // we generate the following:
915            //
916            // * bool <- ty etor: `{ty}_matches_foo`
917            // * ty <- ty etor: `{ty}_extract_foo`
918            // * () <- ty etor: `{ty}_when_foo`
919            // * () <- ty etor: `{ty}_when_not_foo`
920            //
921            // The last three are defined as local extractors that are
922            // implemented in terms of the first. This gives the ISLE compiler
923            // visibility into the extractors' overlapping-ness.
924            if op.etors {
925                debug_assert_eq!(op.args.len(), 1);
926                debug_assert_eq!(op.args[0].1, ty);
927                debug_assert_eq!(op.ret, "bool");
928                debug_assert!(op.name.starts_with("is_"));
929
930                // Cut of the `is_` prefix.
931                let base_name = &op.name[3..];
932                debug_assert!(base_name.len() > 0);
933
934                fmtln!(isle, "(decl pure {ty}_matches_{base_name} (bool) {ty})");
935                fmtln!(
936                    isle,
937                    "(extern extractor {ty}_matches_{base_name} {ty}_matches_{base_name})"
938                );
939                fmtln!(rust, "#[inline]");
940                fmtln!(
941                    rust,
942                    "fn {ty}_matches_{base_name}(&mut self, a: {ty}) -> Option<bool> {{"
943                );
944                rust.indent(|rust| {
945                    fmtln!(rust, "Some({body})");
946                });
947                fmtln!(rust, "}}");
948
949                fmtln!(isle, "(decl pure {ty}_extract_{base_name} ({ty}) {ty})");
950                fmtln!(
951                    isle,
952                    "(extractor ({ty}_extract_{base_name} x) (and ({ty}_matches_{base_name} true) x))"
953                );
954
955                fmtln!(isle, "(decl pure {ty}_when_{base_name} () {ty})");
956                fmtln!(
957                    isle,
958                    "(extractor ({ty}_when_{base_name}) ({ty}_matches_{base_name} true))"
959                );
960
961                fmtln!(isle, "(decl pure {ty}_when_not_{base_name} () {ty})");
962                fmtln!(
963                    isle,
964                    "(extractor ({ty}_when_not_{base_name}) ({ty}_matches_{base_name} false))"
965                );
966            }
967
968            isle.empty_line();
969            rust.empty_line();
970        }
971    }
972
973    // Numeric type conversions.
974    //
975    // Naming and conventions:
976    //
977    // * Constructors:
978    //   * "<from>_into_<to>" for lossless, infallible conversion
979    //   * "<from>_try_into_<to>" for lossless, fallible conversions (exposed as
980    //     partial constructors)
981    //   * "<from>_unwrap_into_<to>" for lossless, fallible conversions that will
982    //     panic at runtime if the conversion would be lossy
983    //   * "<from>_truncate_into_<to>" for lossy, infallible conversions that
984    //     ignore upper bits
985    //   * "<from>_cast_[un]signed" for signed-to-unsigned (and vice versa)
986    //     reinterpretation
987    // * Extractors:
988    //   * "<to>_from_<from>" for both fallible and infallible extractors
989    //   * No unwrapping extractors
990    //   * No truncating extractors
991    //   * No signed-to-unsigned reinterpreting extractors
992    for from in NumericType::all() {
993        for to in NumericType::all() {
994            if from == to {
995                continue;
996            }
997
998            let from_name = from.name();
999            let to_name = to.name();
1000
1001            let lossy = match (from.byte_width.cmp(&to.byte_width), from.signed, to.signed) {
1002                // Widening with the same signedness is lossless.
1003                (Ordering::Less, true, true) | (Ordering::Less, false, false) => false,
1004                // Widening from unsigned to signed is lossless.
1005                (Ordering::Less, false, true) => false,
1006                // Widening from signed to unsigned is lossy.
1007                (Ordering::Less, true, false) => true,
1008                // Same width means we must be changing sign, since we skip
1009                // `from == to`, and this is lossy.
1010                (Ordering::Equal, _, _) => {
1011                    debug_assert_ne!(from.signed, to.signed);
1012                    true
1013                }
1014                // Narrowing is always lossy.
1015                (Ordering::Greater, _, _) => true,
1016            };
1017
1018            let (ctor, partial, rust_ret) = if lossy {
1019                (
1020                    "try_into",
1021                    " partial",
1022                    Cow::from(format!("Option<{to_name}>")),
1023                )
1024            } else {
1025                ("into", "", Cow::from(to_name))
1026            };
1027
1028            // Constructor.
1029            fmtln!(
1030                isle,
1031                "(decl pure{partial} {from_name}_{ctor}_{to_name} ({from_name}) {to_name})"
1032            );
1033            fmtln!(
1034                isle,
1035                "(extern constructor {from_name}_{ctor}_{to_name} {from_name}_{ctor}_{to_name})"
1036            );
1037            if !lossy {
1038                fmtln!(
1039                    isle,
1040                    "(convert {from_name} {to_name} {from_name}_{ctor}_{to_name})"
1041                );
1042            }
1043            fmtln!(rust, "#[inline]");
1044            fmtln!(
1045                rust,
1046                "fn {from_name}_{ctor}_{to_name}(&mut self, x: {from_name}) -> {rust_ret} {{"
1047            );
1048            rust.indent(|rust| {
1049                if lossy {
1050                    fmtln!(rust, "{to_name}::try_from(x).ok()");
1051                } else {
1052                    fmtln!(rust, "{to_name}::from(x)");
1053                }
1054            });
1055            fmtln!(rust, "}}");
1056
1057            // Unwrapping constructor.
1058            if lossy {
1059                fmtln!(
1060                    isle,
1061                    "(decl pure {from_name}_unwrap_into_{to_name} ({from_name}) {to_name})"
1062                );
1063                fmtln!(
1064                    isle,
1065                    "(extern constructor {from_name}_unwrap_into_{to_name} {from_name}_unwrap_into_{to_name})"
1066                );
1067                fmtln!(rust, "#[inline]");
1068                fmtln!(
1069                    rust,
1070                    "fn {from_name}_unwrap_into_{to_name}(&mut self, x: {from_name}) -> {to_name} {{"
1071                );
1072                rust.indent(|rust| {
1073                    fmtln!(rust, "{to_name}::try_from(x).unwrap()");
1074                });
1075                fmtln!(rust, "}}");
1076            }
1077
1078            // Truncating constructor.
1079            if lossy && from.signed == to.signed {
1080                fmtln!(
1081                    isle,
1082                    "(decl pure {from_name}_truncate_into_{to_name} ({from_name}) {to_name})"
1083                );
1084                fmtln!(
1085                    isle,
1086                    "(extern constructor {from_name}_truncate_into_{to_name} {from_name}_truncate_into_{to_name})"
1087                );
1088                fmtln!(rust, "#[inline]");
1089                fmtln!(
1090                    rust,
1091                    "fn {from_name}_truncate_into_{to_name}(&mut self, x: {from_name}) -> {to_name} {{"
1092                );
1093                rust.indent(|rust| {
1094                    fmtln!(rust, "x as {to_name}");
1095                });
1096                fmtln!(rust, "}}");
1097            }
1098
1099            // Signed-to-unsigned reinterpreting constructor.
1100            if from.byte_width == to.byte_width {
1101                debug_assert_ne!(from.signed, to.signed);
1102                let cast_name = if to.signed {
1103                    "cast_signed"
1104                } else {
1105                    "cast_unsigned"
1106                };
1107                fmtln!(
1108                    isle,
1109                    "(decl pure {from_name}_{cast_name} ({from_name}) {to_name})"
1110                );
1111                fmtln!(
1112                    isle,
1113                    "(extern constructor {from_name}_{cast_name} {from_name}_{cast_name})"
1114                );
1115                fmtln!(rust, "#[inline]");
1116                fmtln!(
1117                    rust,
1118                    "fn {from_name}_{cast_name}(&mut self, x: {from_name}) -> {to_name} {{"
1119                );
1120                rust.indent(|rust| {
1121                    // TODO: Once our MSRV is >= 1.87, we should use
1122                    // `x.cast_[un]signed()` here.
1123                    fmtln!(rust, "x as {to_name}");
1124                });
1125                fmtln!(rust, "}}");
1126            }
1127
1128            // Extractor.
1129            fmtln!(
1130                isle,
1131                "(decl pure {to_name}_from_{from_name} ({to_name}) {from_name})"
1132            );
1133            fmtln!(
1134                isle,
1135                "(extern extractor {to_name}_from_{from_name} {from_name}_from_{to_name})"
1136            );
1137            fmtln!(rust, "#[inline]");
1138            fmtln!(
1139                rust,
1140                "fn {from_name}_from_{to_name}(&mut self, x: {from_name}) -> Option<{to_name}> {{"
1141            );
1142            rust.indent(|rust| {
1143                if lossy {
1144                    fmtln!(rust, "x.try_into().ok()");
1145                } else {
1146                    fmtln!(rust, "Some(x.into())");
1147                }
1148            });
1149            fmtln!(rust, "}}");
1150
1151            isle.empty_line();
1152            rust.empty_line();
1153        }
1154    }
1155
1156    rust.indent_pop();
1157    fmtln!(rust, "}}");
1158    rust.indent_pop();
1159    fmtln!(rust, "}}");
1160}
1161
1162pub(crate) fn generate(
1163    formats: &[Rc<InstructionFormat>],
1164    all_inst: &AllInstructions,
1165    isle_numerics_filename: &str,
1166    rust_numerics_filename: &str,
1167    isle_opt_filename: &str,
1168    isle_lower_filename: &str,
1169    isle_dir: &std::path::Path,
1170) -> Result<(), error::Error> {
1171    // Numerics
1172    let mut isle_fmt = Formatter::new(Language::Isle);
1173    let mut rust_fmt = Formatter::new(Language::Rust);
1174    gen_numerics_isle(&mut isle_fmt, &mut rust_fmt);
1175    isle_fmt.write(isle_numerics_filename, isle_dir)?;
1176    rust_fmt.write(rust_numerics_filename, isle_dir)?;
1177
1178    // ISLE DSL: mid-end ("opt") generated bindings.
1179    let mut fmt = Formatter::new(Language::Isle);
1180    gen_opt_isle(&formats, all_inst, &mut fmt);
1181    fmt.write(isle_opt_filename, isle_dir)?;
1182
1183    // ISLE DSL: lowering generated bindings.
1184    let mut fmt = Formatter::new(Language::Isle);
1185    gen_lower_isle(&formats, all_inst, &mut fmt);
1186    fmt.write(isle_lower_filename, isle_dir)?;
1187
1188    Ok(())
1189}