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