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