Skip to main content

cranelift_codegen_meta/
gen_isle.rs

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