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