Path: blob/main/cranelift/codegen/meta/src/gen_isle.rs
1692 views
use crate::cdsl::formats::InstructionFormat;1use crate::cdsl::instructions::AllInstructions;2use crate::error;3use cranelift_srcgen::{Formatter, Language, fmtln};4use std::{borrow::Cow, cmp::Ordering, rc::Rc};56/// Which ISLE target are we generating code for?7#[derive(Clone, Copy, PartialEq, Eq)]8enum IsleTarget {9/// Generating code for instruction selection and lowering.10Lower,11/// Generating code for CLIF to CLIF optimizations.12Opt,13}1415fn gen_common_isle(16formats: &[Rc<InstructionFormat>],17instructions: &AllInstructions,18fmt: &mut Formatter,19isle_target: IsleTarget,20) {21use std::collections::{BTreeMap, BTreeSet};22use std::fmt::Write;2324use crate::cdsl::formats::FormatField;2526fmt.multi_line(27r#"28;; GENERATED BY `gen_isle`. DO NOT EDIT!!!29;;30;; This ISLE file defines all the external type declarations for Cranelift's31;; data structures that ISLE will process, such as `InstructionData` and32;; `Opcode`.33"#,34);35fmt.empty_line();3637// Collect and deduplicate the immediate types from the instruction fields.38let rust_name = |f: &FormatField| f.kind.rust_type.rsplit("::").next().unwrap();39let fields = |f: &FormatField| f.kind.fields.clone();40let immediate_types: BTreeMap<_, _> = formats41.iter()42.flat_map(|f| {43f.imm_fields44.iter()45.map(|i| (rust_name(i), fields(i)))46.collect::<Vec<_>>()47})48.collect();4950// Separate the `enum` immediates (e.g., `FloatCC`) from other kinds of51// immediates.52let (enums, others): (BTreeMap<_, _>, BTreeMap<_, _>) = immediate_types53.iter()54.partition(|(_, field)| field.enum_values().is_some());5556// Generate all the extern type declarations we need for the non-`enum`57// immediates.58fmt.line(";;;; Extern type declarations for immediates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");59fmt.empty_line();60for ty in others.keys() {61fmtln!(fmt, "(type {} (primitive {}))", ty, ty);62}63fmt.empty_line();6465// Generate the `enum` immediates, expanding all of the available variants66// into ISLE.67for (name, field) in enums {68let field = field.enum_values().expect("only enums considered here");69let variants = field.values().cloned().collect();70gen_isle_enum(name, variants, fmt)71}7273// Generate all of the value arrays we need for `InstructionData` as well as74// the constructors and extractors for them.75fmt.line(";;;; Value Arrays ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");76fmt.empty_line();77let value_array_arities: BTreeSet<_> = formats78.iter()79.filter(|f| f.typevar_operand.is_some() && !f.has_value_list && f.num_value_operands != 1)80.map(|f| f.num_value_operands)81.collect();82for n in value_array_arities {83fmtln!(fmt, ";; ISLE representation of `[Value; {}]`.", n);84fmtln!(fmt, "(type ValueArray{} extern (enum))", n);85fmt.empty_line();8687fmtln!(88fmt,89"(decl value_array_{} ({}) ValueArray{})",90n,91(0..n).map(|_| "Value").collect::<Vec<_>>().join(" "),92n93);94fmtln!(95fmt,96"(extern constructor value_array_{} pack_value_array_{})",97n,98n99);100fmtln!(101fmt,102"(extern extractor infallible value_array_{} unpack_value_array_{})",103n,104n105);106fmt.empty_line();107}108109// Generate all of the block arrays we need for `InstructionData` as well as110// the constructors and extractors for them.111fmt.line(";;;; Block Arrays ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");112fmt.empty_line();113let block_array_arities: BTreeSet<_> = formats114.iter()115.filter(|f| f.num_block_operands > 1)116.map(|f| f.num_block_operands)117.collect();118for n in block_array_arities {119fmtln!(fmt, ";; ISLE representation of `[BlockCall; {}]`.", n);120fmtln!(fmt, "(type BlockArray{} extern (enum))", n);121fmt.empty_line();122123fmtln!(124fmt,125"(decl block_array_{0} ({1}) BlockArray{0})",126n,127(0..n).map(|_| "BlockCall").collect::<Vec<_>>().join(" ")128);129130fmtln!(131fmt,132"(extern constructor block_array_{0} pack_block_array_{0})",133n134);135136fmtln!(137fmt,138"(extern extractor infallible block_array_{0} unpack_block_array_{0})",139n140);141fmt.empty_line();142}143144// Raw block entities.145fmtln!(fmt, "(type Block extern (enum))");146fmt.empty_line();147148// Generate the extern type declaration for `Opcode`.149fmt.line(";;;; `Opcode` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");150fmt.empty_line();151fmt.line("(type Opcode extern");152fmt.indent(|fmt| {153fmt.line("(enum");154fmt.indent(|fmt| {155for inst in instructions {156fmtln!(fmt, "{}", inst.camel_name);157}158});159fmt.line(")");160});161fmt.line(")");162fmt.empty_line();163164// Generate the extern type declaration for `InstructionData`.165fmtln!(166fmt,167";;;; `InstructionData` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",168);169fmt.empty_line();170fmtln!(fmt, "(type InstructionData extern");171fmt.indent(|fmt| {172fmt.line("(enum");173fmt.indent(|fmt| {174for format in formats {175let mut s = format!("({} (opcode Opcode)", format.name);176if format.has_value_list {177s.push_str(" (args ValueList)");178} else if format.num_value_operands == 1 {179s.push_str(" (arg Value)");180} else if format.num_value_operands > 1 {181write!(&mut s, " (args ValueArray{})", format.num_value_operands).unwrap();182}183184match format.num_block_operands {1850 => (),1861 => write!(&mut s, " (destination BlockCall)").unwrap(),187n => write!(&mut s, " (blocks BlockArray{n})").unwrap(),188}189190match format.num_raw_block_operands {1910 => (),1921 => write!(&mut s, "(block Block)").unwrap(),193_ => panic!("Too many raw block arguments"),194}195196for field in &format.imm_fields {197write!(198&mut s,199" ({} {})",200field.member,201field.kind.rust_type.rsplit("::").next().unwrap()202)203.unwrap();204}205s.push(')');206fmt.line(&s);207}208});209fmt.line(")");210});211fmt.line(")");212fmt.empty_line();213214// Generate the helper extractors for each opcode's full instruction.215fmtln!(216fmt,217";;;; Extracting Opcode, Operands, and Immediates from `InstructionData` ;;;;;;;;",218);219fmt.empty_line();220for inst in instructions {221let results_len = inst.value_results.len();222let is_var_args = inst.format.has_value_list;223let has_side_effects = inst.can_trap || inst.other_side_effects;224225let (ret_ty, ty_in_decl, make_inst_ctor, inst_data_etor) =226match (isle_target, is_var_args, results_len, has_side_effects) {227// The mid-end does not deal with instructions that have var-args right now.228(IsleTarget::Opt, true, _, _) => continue,229230(IsleTarget::Opt, _, 1, false) => ("Value", true, "make_inst", "inst_data_value"),231(IsleTarget::Opt, _, _, _) => ("Inst", false, "make_skeleton_inst", "inst_data"),232(IsleTarget::Lower, _, _, _) => ("Inst", false, "make_inst", "inst_data_value"),233};234235fmtln!(236fmt,237"(decl {} ({}{}) {})",238inst.name,239if ty_in_decl { "Type " } else { "" },240inst.operands_in241.iter()242.map(|o| {243let ty = o.kind.rust_type;244if ty == "&[Value]" {245"ValueSlice"246} else {247ty.rsplit("::").next().unwrap()248}249})250.collect::<Vec<_>>()251.join(" "),252ret_ty253);254fmtln!(fmt, "(extractor");255fmt.indent(|fmt| {256fmtln!(257fmt,258"({} {}{})",259inst.name,260if ty_in_decl { "ty " } else { "" },261inst.operands_in262.iter()263.map(|o| { o.name })264.collect::<Vec<_>>()265.join(" ")266);267268let mut s = format!(269"({inst_data_etor} {}(InstructionData.{} (Opcode.{})",270if ty_in_decl { "ty " } else { "" },271inst.format.name,272inst.camel_name273);274275// Value and varargs operands.276if inst.format.has_value_list {277// The instruction format uses a value list, but the278// instruction itself might have not only a `&[Value]`279// varargs operand, but also one or more `Value` operands as280// well. If this is the case, then we need to read them off281// the front of the `ValueList`.282let values: Vec<_> = inst283.operands_in284.iter()285.filter(|o| o.is_value())286.map(|o| o.name)287.collect();288let varargs = inst289.operands_in290.iter()291.find(|o| o.is_varargs())292.unwrap()293.name;294if values.is_empty() {295write!(&mut s, " (value_list_slice {varargs})").unwrap();296} else {297write!(298&mut s,299" (unwrap_head_value_list_{} {} {})",300values.len(),301values.join(" "),302varargs303)304.unwrap();305}306} else if inst.format.num_value_operands == 1 {307write!(308&mut s,309" {}",310inst.operands_in.iter().find(|o| o.is_value()).unwrap().name311)312.unwrap();313} else if inst.format.num_value_operands > 1 {314let values = inst315.operands_in316.iter()317.filter(|o| o.is_value())318.map(|o| o.name)319.collect::<Vec<_>>();320assert_eq!(values.len(), inst.format.num_value_operands);321let values = values.join(" ");322write!(323&mut s,324" (value_array_{} {})",325inst.format.num_value_operands, values,326)327.unwrap();328}329330// Immediates.331let imm_operands: Vec<_> = inst332.operands_in333.iter()334.filter(|o| {335!o.is_value() && !o.is_varargs() && !o.kind.is_block() && !o.kind.is_raw_block()336})337.collect();338assert_eq!(imm_operands.len(), inst.format.imm_fields.len(),);339for op in imm_operands {340write!(&mut s, " {}", op.name).unwrap();341}342343// Blocks.344let block_operands: Vec<_> = inst345.operands_in346.iter()347.filter(|o| o.kind.is_block())348.collect();349assert_eq!(block_operands.len(), inst.format.num_block_operands);350assert!(block_operands.len() <= 2);351352if !block_operands.is_empty() {353if block_operands.len() == 1 {354write!(&mut s, " {}", block_operands[0].name).unwrap();355} else {356let blocks: Vec<_> = block_operands.iter().map(|o| o.name).collect();357let blocks = blocks.join(" ");358write!(359&mut s,360" (block_array_{} {})",361inst.format.num_block_operands, blocks,362)363.unwrap();364}365}366367// Raw blocks.368match inst.format.num_raw_block_operands {3690 => {}3701 => {371write!(&mut s, " block").unwrap();372}373_ => panic!("Too many raw block arguments"),374}375376s.push_str("))");377fmt.line(&s);378});379fmt.line(")");380381// Generate a constructor if this is the mid-end prelude.382if isle_target == IsleTarget::Opt {383fmtln!(384fmt,385"(rule ({}{} {})",386inst.name,387if ty_in_decl { " ty" } else { "" },388inst.operands_in389.iter()390.map(|o| o.name)391.collect::<Vec<_>>()392.join(" ")393);394fmt.indent(|fmt| {395let mut s = format!(396"({make_inst_ctor}{} (InstructionData.{} (Opcode.{})",397if ty_in_decl { " ty" } else { "" },398inst.format.name,399inst.camel_name400);401402// Handle values. Note that we skip generating403// constructors for any instructions with variadic404// value lists. This is fine for the mid-end because405// in practice only calls and branches (for branch406// args) use this functionality, and neither can407// really be optimized or rewritten in the mid-end408// (currently).409//410// As a consequence, we only have to handle the411// one-`Value` case, in which the `Value` is directly412// in the `InstructionData`, and the multiple-`Value`413// case, in which the `Value`s are in a414// statically-sized array (e.g. `[Value; 2]` for a415// binary op).416assert!(!inst.format.has_value_list);417if inst.format.num_value_operands == 1 {418write!(419&mut s,420" {}",421inst.operands_in.iter().find(|o| o.is_value()).unwrap().name422)423.unwrap();424} else if inst.format.num_value_operands > 1 {425// As above, get all bindings together, and pass426// to a sub-term; here we use a constructor to427// build the value array.428let values = inst429.operands_in430.iter()431.filter(|o| o.is_value())432.map(|o| o.name)433.collect::<Vec<_>>();434assert_eq!(values.len(), inst.format.num_value_operands);435let values = values.join(" ");436write!(437&mut s,438" (value_array_{}_ctor {})",439inst.format.num_value_operands, values440)441.unwrap();442}443444if inst.format.num_block_operands > 0 {445let blocks: Vec<_> = inst446.operands_in447.iter()448.filter(|o| o.kind.is_block())449.map(|o| o.name)450.collect();451if inst.format.num_block_operands == 1 {452write!(&mut s, " {}", blocks.first().unwrap(),).unwrap();453} else {454write!(455&mut s,456" (block_array_{} {})",457inst.format.num_block_operands,458blocks.join(" ")459)460.unwrap();461}462}463464match inst.format.num_raw_block_operands {4650 => {}4661 => {467write!(&mut s, " block").unwrap();468}469_ => panic!("Too many raw block arguments"),470}471472// Immediates (non-value args).473for o in inst.operands_in.iter().filter(|o| {474!o.is_value() && !o.is_varargs() && !o.kind.is_block() && !o.kind.is_raw_block()475}) {476write!(&mut s, " {}", o.name).unwrap();477}478s.push_str("))");479fmt.line(&s);480});481fmt.line(")");482}483484fmt.empty_line();485}486}487488fn gen_opt_isle(489formats: &[Rc<InstructionFormat>],490instructions: &AllInstructions,491fmt: &mut Formatter,492) {493gen_common_isle(formats, instructions, fmt, IsleTarget::Opt);494}495496fn gen_lower_isle(497formats: &[Rc<InstructionFormat>],498instructions: &AllInstructions,499fmt: &mut Formatter,500) {501gen_common_isle(formats, instructions, fmt, IsleTarget::Lower);502}503504/// Generate an `enum` immediate in ISLE.505fn gen_isle_enum(name: &str, mut variants: Vec<&str>, fmt: &mut Formatter) {506variants.sort();507let prefix = format!(";;;; Enumerated Immediate: {name} ");508fmtln!(fmt, "{:;<80}", prefix);509fmt.empty_line();510fmtln!(fmt, "(type {} extern", name);511fmt.indent(|fmt| {512fmt.line("(enum");513fmt.indent(|fmt| {514for variant in variants {515fmtln!(fmt, "{}", variant);516}517});518fmt.line(")");519});520fmt.line(")");521fmt.empty_line();522}523524#[derive(Clone, Copy, PartialEq, Eq)]525struct NumericType {526signed: bool,527byte_width: u8,528}529530impl NumericType {531fn all() -> impl Iterator<Item = NumericType> {532[1, 2, 4, 8, 16].into_iter().flat_map(|byte_width| {533[true, false]534.into_iter()535.map(move |signed| NumericType { signed, byte_width })536})537}538539fn name(&self) -> &'static str {540let idx = self.byte_width.ilog2();541let idx = usize::try_from(idx).unwrap();542if self.signed {543["i8", "i16", "i32", "i64", "i128"][idx]544} else {545["u8", "u16", "u32", "u64", "u128"][idx]546}547}548}549550#[derive(Clone, Default, PartialEq, Eq)]551struct NumericOp<'a> {552/// The name of this operation.553name: &'a str,554/// The return type of this operation.555ret: &'a str,556/// Whether this operation is partial.557partial: bool,558/// (name, type) pairs of arguments.559args: Rc<[(&'a str, &'a str)]>,560/// The source text for the constructor's body.561body: &'a str,562/// Whether extractors should be generated for this op.563///564/// Must have `arity == 1`, `ret == bool`, and `name.starts_with("is_")`.565etors: bool,566}567568impl NumericOp<'_> {569fn ops_for_type(ty: &NumericType) -> impl Iterator<Item = NumericOp<'_>> {570let arity1 = NumericOp {571args: [("a", ty.name())].into(),572..NumericOp::default()573};574575let arity2 = NumericOp {576args: [("a", ty.name()), ("b", ty.name())].into(),577..NumericOp::default()578};579580let comparison = NumericOp {581ret: "bool",582..arity2.clone()583};584585let predicate = NumericOp {586ret: "bool",587etors: true,588..arity1.clone()589};590591let binop = NumericOp {592ret: ty.name(),593..arity2.clone()594};595596let partial_binop = NumericOp {597ret: ty.name(),598partial: true,599..binop.clone()600};601602let unop = NumericOp {603ret: ty.name(),604..arity1.clone()605};606607let partial_unop = NumericOp {608ret: ty.name(),609partial: true,610..unop.clone()611};612613let shift = NumericOp {614args: [("a", ty.name()), ("b", "u32")].into(),615..binop.clone()616};617618let partial_shift = NumericOp {619args: [("a", ty.name()), ("b", "u32")].into(),620..partial_binop.clone()621};622623// Operations that apply to both signed and unsigned numbers.624let ops = [625// Comparisons.626NumericOp {627name: "eq",628body: "a == b",629..comparison.clone()630},631NumericOp {632name: "ne",633body: "a != b",634..comparison.clone()635},636NumericOp {637name: "lt",638body: "a < b",639..comparison.clone()640},641NumericOp {642name: "lt_eq",643body: "a <= b",644..comparison.clone()645},646NumericOp {647name: "gt",648body: "a > b",649..comparison.clone()650},651NumericOp {652name: "gt_eq",653body: "a >= b",654..comparison.clone()655},656// Arithmetic operations.657//658// For each operation (e.g. addition) we have three variants:659//660// * partial ctor `checked_add`: no return value on overflow661// * ctor `wrapping_add`: wraps on overflow662// * ctor `add`: non-partial but panics at runtime on overflow663NumericOp {664name: "checked_add",665body: "a.checked_add(b)",666..partial_binop.clone()667},668NumericOp {669name: "wrapping_add",670body: "a.wrapping_add(b)",671..binop.clone()672},673NumericOp {674name: "add",675body: r#"a.checked_add(b).unwrap_or_else(|| panic!("addition overflow: {a} + {b}"))"#,676..binop.clone()677},678NumericOp {679name: "checked_sub",680body: "a.checked_sub(b)",681..partial_binop.clone()682},683NumericOp {684name: "wrapping_sub",685body: "a.wrapping_sub(b)",686..binop.clone()687},688NumericOp {689name: "sub",690body: r#"a.checked_sub(b).unwrap_or_else(|| panic!("subtraction overflow: {a} - {b}"))"#,691..binop.clone()692},693NumericOp {694name: "checked_mul",695body: "a.checked_mul(b)",696..partial_binop.clone()697},698NumericOp {699name: "wrapping_mul",700body: "a.wrapping_mul(b)",701..binop.clone()702},703NumericOp {704name: "mul",705body: r#"a.checked_mul(b).unwrap_or_else(|| panic!("multiplication overflow: {a} * {b}"))"#,706..binop.clone()707},708NumericOp {709name: "checked_div",710body: "a.checked_div(b)",711..partial_binop.clone()712},713NumericOp {714name: "wrapping_div",715body: "a.wrapping_div(b)",716..binop.clone()717},718NumericOp {719name: "div",720body: r#"a.checked_div(b).unwrap_or_else(|| panic!("div failure: {a} / {b}"))"#,721..binop.clone()722},723NumericOp {724name: "checked_rem",725body: "a.checked_rem(b)",726..partial_binop.clone()727},728NumericOp {729name: "rem",730body: r#"a.checked_rem(b).unwrap_or_else(|| panic!("rem failure: {a} % {b}"))"#,731..binop.clone()732},733// Bitwise operations.734//735// When applicable (e.g. shifts) we have checked, wrapping, and736// unwrapping variants, similar to arithmetic operations.737NumericOp {738name: "and",739body: "a & b",740..binop.clone()741},742NumericOp {743name: "or",744body: "a | b",745..binop.clone()746},747NumericOp {748name: "xor",749body: "a ^ b",750..binop.clone()751},752NumericOp {753name: "not",754body: "!a",755..unop.clone()756},757NumericOp {758name: "checked_shl",759body: "a.checked_shl(b)",760..partial_shift.clone()761},762NumericOp {763name: "wrapping_shl",764body: "a.wrapping_shl(b)",765..shift.clone()766},767NumericOp {768name: "shl",769body: r#"a.checked_shl(b).unwrap_or_else(|| panic!("shl overflow: {a} << {b}"))"#,770..shift.clone()771},772NumericOp {773name: "checked_shr",774body: "a.checked_shr(b)",775..partial_shift.clone()776},777NumericOp {778name: "wrapping_shr",779body: "a.wrapping_shr(b)",780..shift.clone()781},782NumericOp {783name: "shr",784body: r#"a.checked_shr(b).unwrap_or_else(|| panic!("shr overflow: {a} >> {b}"))"#,785..shift.clone()786},787// Predicates.788//789// We generate both pure constructors and a variety of extractors790// for these. See the relevant comments in `gen_numerics_isle` about791// the extractors.792NumericOp {793name: "is_zero",794body: "a == 0",795..predicate.clone()796},797NumericOp {798name: "is_non_zero",799body: "a != 0",800..predicate.clone()801},802NumericOp {803name: "is_odd",804body: "a & 1 == 1",805..predicate.clone()806},807NumericOp {808name: "is_even",809body: "a & 1 == 0",810..predicate.clone()811},812// Miscellaneous unary operations.813NumericOp {814name: "checked_ilog2",815body: "a.checked_ilog2()",816ret: "u32",817..partial_unop.clone()818},819NumericOp {820name: "ilog2",821body: r#"a.checked_ilog2().unwrap_or_else(|| panic!("ilog2 overflow: {a}"))"#,822ret: "u32",823..unop.clone()824},825NumericOp {826name: "trailing_zeros",827body: "a.trailing_zeros()",828ret: "u32",829..unop.clone()830},831NumericOp {832name: "trailing_ones",833body: "a.trailing_ones()",834ret: "u32",835..unop.clone()836},837NumericOp {838name: "leading_zeros",839body: "a.leading_zeros()",840ret: "u32",841..unop.clone()842},843NumericOp {844name: "leading_ones",845body: "a.leading_ones()",846ret: "u32",847..unop.clone()848},849];850851// Operations that apply only to signed numbers.852let signed_ops = [853NumericOp {854name: "checked_neg",855body: "a.checked_neg()",856..partial_unop.clone()857},858NumericOp {859name: "wrapping_neg",860body: "a.wrapping_neg()",861..unop.clone()862},863NumericOp {864name: "neg",865body: r#"a.checked_neg().unwrap_or_else(|| panic!("negation overflow: {a}"))"#,866..unop.clone()867},868];869870// Operations that apply only to unsigned numbers.871let unsigned_ops = [NumericOp {872name: "is_power_of_two",873body: "a.is_power_of_two()",874..predicate.clone()875}];876877struct IterIf<I> {878condition: bool,879iter: I,880}881882impl<I: Iterator> Iterator for IterIf<I> {883type Item = I::Item;884885fn next(&mut self) -> Option<Self::Item> {886if self.condition {887self.iter.next()888} else {889None890}891}892}893894ops.into_iter()895.chain(IterIf {896condition: ty.signed,897iter: signed_ops.into_iter(),898})899.chain(IterIf {900condition: !ty.signed,901iter: unsigned_ops.into_iter(),902})903}904}905906fn gen_numerics_isle(isle: &mut Formatter, rust: &mut Formatter) {907fmtln!(rust, "#[macro_export]");908fmtln!(rust, "#[doc(hidden)]");909fmtln!(rust, "macro_rules! isle_numerics_methods {{");910rust.indent_push();911fmtln!(rust, "() => {{");912rust.indent_push();913914for ty in NumericType::all() {915for op in NumericOp::ops_for_type(&ty) {916let ty = ty.name();917let op_name = format!("{ty}_{}", op.name);918let partial = if op.partial { " partial" } else { "" };919let ret = op.ret;920fmtln!(isle, "(decl pure{partial} {op_name} (");921isle.indent(|isle| {922for (_arg_name, arg_ty) in op.args.iter() {923fmtln!(isle, "{arg_ty}");924}925});926fmtln!(isle, ") {ret})");927fmtln!(isle, "(extern constructor {op_name} {op_name})");928929let ret = if op.partial {930Cow::from(format!("Option<{ret}>"))931} else {932Cow::from(ret)933};934let body = op.body;935fmtln!(rust, "#[inline]");936fmtln!(rust, "fn {op_name}(");937rust.indent(|rust| {938fmtln!(rust, "&mut self,");939for (arg_name, arg_ty) in op.args.iter() {940fmtln!(rust, "{arg_name}: {arg_ty},");941}942});943fmtln!(rust, ") -> {ret} {{");944rust.indent(|rust| {945fmtln!(rust, "{body}");946});947fmtln!(rust, "}}");948949// When generating extractors for a `{ty}_is_foo` predicate,950// we generate the following:951//952// * bool <- ty etor: `{ty}_matches_foo`953// * ty <- ty etor: `{ty}_extract_foo`954// * () <- ty etor: `{ty}_when_foo`955// * () <- ty etor: `{ty}_when_not_foo`956//957// The last three are defined as local extractors that are958// implemented in terms of the first. This gives the ISLE compiler959// visibility into the extractors' overlapping-ness.960if op.etors {961debug_assert_eq!(op.args.len(), 1);962debug_assert_eq!(op.args[0].1, ty);963debug_assert_eq!(op.ret, "bool");964debug_assert!(op.name.starts_with("is_"));965966// Cut of the `is_` prefix.967let base_name = &op.name[3..];968debug_assert!(base_name.len() > 0);969970fmtln!(isle, "(decl pure {ty}_matches_{base_name} (bool) {ty})");971fmtln!(972isle,973"(extern extractor {ty}_matches_{base_name} {ty}_matches_{base_name})"974);975fmtln!(rust, "#[inline]");976fmtln!(977rust,978"fn {ty}_matches_{base_name}(&mut self, a: {ty}) -> Option<bool> {{"979);980rust.indent(|rust| {981fmtln!(rust, "Some({body})");982});983fmtln!(rust, "}}");984985fmtln!(isle, "(decl pure {ty}_extract_{base_name} ({ty}) {ty})");986fmtln!(987isle,988"(extractor ({ty}_extract_{base_name} x) (and ({ty}_matches_{base_name} true) x))"989);990991fmtln!(isle, "(decl pure {ty}_when_{base_name} () {ty})");992fmtln!(993isle,994"(extractor ({ty}_when_{base_name}) ({ty}_matches_{base_name} true))"995);996997fmtln!(isle, "(decl pure {ty}_when_not_{base_name} () {ty})");998fmtln!(999isle,1000"(extractor ({ty}_when_not_{base_name}) ({ty}_matches_{base_name} false))"1001);1002}10031004isle.empty_line();1005rust.empty_line();1006}1007}10081009// Numeric type conversions.1010//1011// Naming and conventions:1012//1013// * Constructors:1014// * "<from>_into_<to>" for lossless, infallible conversion1015// * "<from>_try_into_<to>" for lossless, fallible conversions (exposed as1016// partial constructors)1017// * "<from>_unwrap_into_<to>" for lossless, fallible conversions that will1018// panic at runtime if the conversion would be lossy1019// * "<from>_truncate_into_<to>" for lossy, infallible conversions that1020// ignore upper bits1021// * "<from>_cast_[un]signed" for signed-to-unsigned (and vice versa)1022// reinterpretation1023// * Extractors:1024// * "<to>_from_<from>" for both fallible and infallible extractors1025// * No unwrapping extractors1026// * No truncating extractors1027// * No signed-to-unsigned reinterpreting extractors1028for from in NumericType::all() {1029for to in NumericType::all() {1030if from == to {1031continue;1032}10331034let from_name = from.name();1035let to_name = to.name();10361037let lossy = match (from.byte_width.cmp(&to.byte_width), from.signed, to.signed) {1038// Widening with the same signedness is lossless.1039(Ordering::Less, true, true) | (Ordering::Less, false, false) => false,1040// Widening from unsigned to signed is lossless.1041(Ordering::Less, false, true) => false,1042// Widening from signed to unsigned is lossy.1043(Ordering::Less, true, false) => true,1044// Same width means we must be changing sign, since we skip1045// `from == to`, and this is lossy.1046(Ordering::Equal, _, _) => {1047debug_assert_ne!(from.signed, to.signed);1048true1049}1050// Narrowing is always lossy.1051(Ordering::Greater, _, _) => true,1052};10531054let (ctor, partial, rust_ret) = if lossy {1055(1056"try_into",1057" partial",1058Cow::from(format!("Option<{to_name}>")),1059)1060} else {1061("into", "", Cow::from(to_name))1062};10631064// Constructor.1065fmtln!(1066isle,1067"(decl pure{partial} {from_name}_{ctor}_{to_name} ({from_name}) {to_name})"1068);1069fmtln!(1070isle,1071"(extern constructor {from_name}_{ctor}_{to_name} {from_name}_{ctor}_{to_name})"1072);1073if !lossy {1074fmtln!(1075isle,1076"(convert {from_name} {to_name} {from_name}_{ctor}_{to_name})"1077);1078}1079fmtln!(rust, "#[inline]");1080fmtln!(1081rust,1082"fn {from_name}_{ctor}_{to_name}(&mut self, x: {from_name}) -> {rust_ret} {{"1083);1084rust.indent(|rust| {1085if lossy {1086fmtln!(rust, "{to_name}::try_from(x).ok()");1087} else {1088fmtln!(rust, "{to_name}::from(x)");1089}1090});1091fmtln!(rust, "}}");10921093// Unwrapping constructor.1094if lossy {1095fmtln!(1096isle,1097"(decl pure {from_name}_unwrap_into_{to_name} ({from_name}) {to_name})"1098);1099fmtln!(1100isle,1101"(extern constructor {from_name}_unwrap_into_{to_name} {from_name}_unwrap_into_{to_name})"1102);1103fmtln!(rust, "#[inline]");1104fmtln!(1105rust,1106"fn {from_name}_unwrap_into_{to_name}(&mut self, x: {from_name}) -> {to_name} {{"1107);1108rust.indent(|rust| {1109fmtln!(rust, "{to_name}::try_from(x).unwrap()");1110});1111fmtln!(rust, "}}");1112}11131114// Truncating constructor.1115if lossy && from.signed == to.signed {1116fmtln!(1117isle,1118"(decl pure {from_name}_truncate_into_{to_name} ({from_name}) {to_name})"1119);1120fmtln!(1121isle,1122"(extern constructor {from_name}_truncate_into_{to_name} {from_name}_truncate_into_{to_name})"1123);1124fmtln!(rust, "#[inline]");1125fmtln!(1126rust,1127"fn {from_name}_truncate_into_{to_name}(&mut self, x: {from_name}) -> {to_name} {{"1128);1129rust.indent(|rust| {1130fmtln!(rust, "x as {to_name}");1131});1132fmtln!(rust, "}}");1133}11341135// Signed-to-unsigned reinterpreting constructor.1136if from.byte_width == to.byte_width {1137debug_assert_ne!(from.signed, to.signed);1138let cast_name = if to.signed {1139"cast_signed"1140} else {1141"cast_unsigned"1142};1143fmtln!(1144isle,1145"(decl pure {from_name}_{cast_name} ({from_name}) {to_name})"1146);1147fmtln!(1148isle,1149"(extern constructor {from_name}_{cast_name} {from_name}_{cast_name})"1150);1151fmtln!(rust, "#[inline]");1152fmtln!(1153rust,1154"fn {from_name}_{cast_name}(&mut self, x: {from_name}) -> {to_name} {{"1155);1156rust.indent(|rust| {1157// TODO: Once our MSRV is >= 1.87, we should use1158// `x.cast_[un]signed()` here.1159fmtln!(rust, "x as {to_name}");1160});1161fmtln!(rust, "}}");1162}11631164// Extractor.1165fmtln!(1166isle,1167"(decl pure {to_name}_from_{from_name} ({to_name}) {from_name})"1168);1169fmtln!(1170isle,1171"(extern extractor {to_name}_from_{from_name} {from_name}_from_{to_name})"1172);1173fmtln!(rust, "#[inline]");1174fmtln!(1175rust,1176"fn {from_name}_from_{to_name}(&mut self, x: {from_name}) -> Option<{to_name}> {{"1177);1178rust.indent(|rust| {1179if lossy {1180fmtln!(rust, "x.try_into().ok()");1181} else {1182fmtln!(rust, "Some(x.into())");1183}1184});1185fmtln!(rust, "}}");11861187isle.empty_line();1188rust.empty_line();1189}1190}11911192rust.indent_pop();1193fmtln!(rust, "}}");1194rust.indent_pop();1195fmtln!(rust, "}}");1196}11971198pub(crate) fn generate(1199formats: &[Rc<InstructionFormat>],1200all_inst: &AllInstructions,1201isle_numerics_filename: &str,1202rust_numerics_filename: &str,1203isle_opt_filename: &str,1204isle_lower_filename: &str,1205isle_dir: &std::path::Path,1206) -> Result<(), error::Error> {1207// Numerics1208let mut isle_fmt = Formatter::new(Language::Isle);1209let mut rust_fmt = Formatter::new(Language::Rust);1210gen_numerics_isle(&mut isle_fmt, &mut rust_fmt);1211isle_fmt.write(isle_numerics_filename, isle_dir)?;1212rust_fmt.write(rust_numerics_filename, isle_dir)?;12131214// ISLE DSL: mid-end ("opt") generated bindings.1215let mut fmt = Formatter::new(Language::Isle);1216gen_opt_isle(&formats, all_inst, &mut fmt);1217fmt.write(isle_opt_filename, isle_dir)?;12181219// ISLE DSL: lowering generated bindings.1220let mut fmt = Formatter::new(Language::Isle);1221gen_lower_isle(&formats, all_inst, &mut fmt);1222fmt.write(isle_lower_filename, isle_dir)?;12231224Ok(())1225}122612271228