Skip to main content

cranelift_codegen_meta/
gen_isle.rs

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