use cranelift_codegen_shared::condcodes::IntCC; use cranelift_entity::{entity_impl, PrimaryMap}; use std::collections::HashMap; use std::fmt; use std::fmt::{Display, Error, Formatter}; use std::rc::Rc; use crate::cdsl::camel_case; use crate::cdsl::formats::{FormatField, InstructionFormat}; use crate::cdsl::operands::Operand; use crate::cdsl::type_inference::Constraint; use crate::cdsl::types::{LaneType, ReferenceType, ValueType, VectorType}; use crate::cdsl::typevar::TypeVar; use crate::shared::formats::Formats; use crate::shared::types::{Bool, Float, Int, Reference}; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub(crate) struct OpcodeNumber(u32); entity_impl!(OpcodeNumber); pub(crate) type AllInstructions = PrimaryMap; pub(crate) struct InstructionGroupBuilder<'all_inst> { all_instructions: &'all_inst mut AllInstructions, own_instructions: Vec, } impl<'all_inst> InstructionGroupBuilder<'all_inst> { pub fn new(all_instructions: &'all_inst mut AllInstructions) -> Self { Self { all_instructions, own_instructions: Vec::new(), } } pub fn push(&mut self, builder: InstructionBuilder) { let opcode_number = OpcodeNumber(self.all_instructions.next_key().as_u32()); let inst = builder.build(opcode_number); // Note this clone is cheap, since Instruction is a Rc<> wrapper for InstructionContent. self.own_instructions.push(inst.clone()); self.all_instructions.push(inst); } pub fn build(self) -> InstructionGroup { InstructionGroup { instructions: self.own_instructions, } } } /// Every instruction must belong to exactly one instruction group. A given /// target architecture can support instructions from multiple groups, and it /// does not necessarily support all instructions in a group. pub(crate) struct InstructionGroup { instructions: Vec, } impl InstructionGroup { pub fn by_name(&self, name: &'static str) -> &Instruction { self.instructions .iter() .find(|inst| inst.name == name) .unwrap_or_else(|| panic!("instruction with name '{}' does not exist", name)) } } /// Instructions can have parameters bound to them to specialize them for more specific encodings /// (e.g. the encoding for adding two float types may be different than that of adding two /// integer types) pub(crate) trait Bindable { /// Bind a parameter to an instruction fn bind(&self, parameter: impl Into) -> BoundInstruction; } #[derive(Debug)] pub(crate) struct PolymorphicInfo { pub use_typevar_operand: bool, pub ctrl_typevar: TypeVar, pub other_typevars: Vec, } #[derive(Debug)] pub(crate) struct InstructionContent { /// Instruction mnemonic, also becomes opcode name. pub name: String, pub camel_name: String, pub opcode_number: OpcodeNumber, /// Documentation string. pub doc: String, /// Input operands. This can be a mix of SSA value operands and other operand kinds. pub operands_in: Vec, /// Output operands. The output operands must be SSA values or `variable_args`. pub operands_out: Vec, /// Instruction-specific TypeConstraints. pub constraints: Vec, /// Instruction format, automatically derived from the input operands. pub format: Rc, /// One of the input or output operands is a free type variable. None if the instruction is not /// polymorphic, set otherwise. pub polymorphic_info: Option, /// Indices in operands_in of input operands that are values. pub value_opnums: Vec, /// Indices in operands_in of input operands that are immediates or entities. pub imm_opnums: Vec, /// Indices in operands_out of output operands that are values. pub value_results: Vec, /// True for instructions that terminate the block. pub is_terminator: bool, /// True for all branch or jump instructions. pub is_branch: bool, /// True for all indirect branch or jump instructions.', pub is_indirect_branch: bool, /// Is this a call instruction? pub is_call: bool, /// Is this a return instruction? pub is_return: bool, /// Is this a ghost instruction? pub is_ghost: bool, /// Can this instruction read from memory? pub can_load: bool, /// Can this instruction write to memory? pub can_store: bool, /// Can this instruction cause a trap? pub can_trap: bool, /// Does this instruction have other side effects besides can_* flags? pub other_side_effects: bool, /// Does this instruction write to CPU flags? pub writes_cpu_flags: bool, /// Should this opcode be considered to clobber all live registers, during regalloc? pub clobbers_all_regs: bool, } impl InstructionContent { pub fn snake_name(&self) -> &str { if &self.name == "return" { "return_" } else { &self.name } } pub fn all_typevars(&self) -> Vec<&TypeVar> { match &self.polymorphic_info { Some(poly) => { let mut result = vec![&poly.ctrl_typevar]; result.extend(&poly.other_typevars); result } None => Vec::new(), } } } pub(crate) type Instruction = Rc; impl Bindable for Instruction { fn bind(&self, parameter: impl Into) -> BoundInstruction { BoundInstruction::new(self).bind(parameter) } } impl fmt::Display for InstructionContent { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { if !self.operands_out.is_empty() { let operands_out = self .operands_out .iter() .map(|op| op.name) .collect::>() .join(", "); fmt.write_str(&operands_out)?; fmt.write_str(" = ")?; } fmt.write_str(&self.name)?; if !self.operands_in.is_empty() { let operands_in = self .operands_in .iter() .map(|op| op.name) .collect::>() .join(", "); fmt.write_str(" ")?; fmt.write_str(&operands_in)?; } Ok(()) } } pub(crate) struct InstructionBuilder { name: String, doc: String, format: Rc, operands_in: Option>, operands_out: Option>, constraints: Option>, // See Instruction comments for the meaning of these fields. is_terminator: bool, is_branch: bool, is_indirect_branch: bool, is_call: bool, is_return: bool, is_ghost: bool, can_load: bool, can_store: bool, can_trap: bool, other_side_effects: bool, clobbers_all_regs: bool, } impl InstructionBuilder { pub fn new>(name: S, doc: S, format: &Rc) -> Self { Self { name: name.into(), doc: doc.into(), format: format.clone(), operands_in: None, operands_out: None, constraints: None, is_terminator: false, is_branch: false, is_indirect_branch: false, is_call: false, is_return: false, is_ghost: false, can_load: false, can_store: false, can_trap: false, other_side_effects: false, clobbers_all_regs: false, } } pub fn operands_in(mut self, operands: Vec<&Operand>) -> Self { assert!(self.operands_in.is_none()); self.operands_in = Some(operands.iter().map(|x| (*x).clone()).collect()); self } pub fn operands_out(mut self, operands: Vec<&Operand>) -> Self { assert!(self.operands_out.is_none()); self.operands_out = Some(operands.iter().map(|x| (*x).clone()).collect()); self } pub fn constraints(mut self, constraints: Vec) -> Self { assert!(self.constraints.is_none()); self.constraints = Some(constraints); self } #[allow(clippy::wrong_self_convention)] pub fn is_terminator(mut self, val: bool) -> Self { self.is_terminator = val; self } #[allow(clippy::wrong_self_convention)] pub fn is_branch(mut self, val: bool) -> Self { self.is_branch = val; self } #[allow(clippy::wrong_self_convention)] pub fn is_indirect_branch(mut self, val: bool) -> Self { self.is_indirect_branch = val; self } #[allow(clippy::wrong_self_convention)] pub fn is_call(mut self, val: bool) -> Self { self.is_call = val; self } #[allow(clippy::wrong_self_convention)] pub fn is_return(mut self, val: bool) -> Self { self.is_return = val; self } #[allow(clippy::wrong_self_convention)] pub fn is_ghost(mut self, val: bool) -> Self { self.is_ghost = val; self } pub fn can_load(mut self, val: bool) -> Self { self.can_load = val; self } pub fn can_store(mut self, val: bool) -> Self { self.can_store = val; self } pub fn can_trap(mut self, val: bool) -> Self { self.can_trap = val; self } pub fn other_side_effects(mut self, val: bool) -> Self { self.other_side_effects = val; self } pub fn clobbers_all_regs(mut self, val: bool) -> Self { self.clobbers_all_regs = val; self } fn build(self, opcode_number: OpcodeNumber) -> Instruction { let operands_in = self.operands_in.unwrap_or_else(Vec::new); let operands_out = self.operands_out.unwrap_or_else(Vec::new); let mut value_opnums = Vec::new(); let mut imm_opnums = Vec::new(); for (i, op) in operands_in.iter().enumerate() { if op.is_value() { value_opnums.push(i); } else if op.is_immediate_or_entityref() { imm_opnums.push(i); } else { assert!(op.is_varargs()); } } let value_results = operands_out .iter() .enumerate() .filter_map(|(i, op)| if op.is_value() { Some(i) } else { None }) .collect(); verify_format(&self.name, &operands_in, &self.format); let polymorphic_info = verify_polymorphic(&operands_in, &operands_out, &self.format, &value_opnums); // Infer from output operands whether an instruction clobbers CPU flags or not. let writes_cpu_flags = operands_out.iter().any(|op| op.is_cpu_flags()); let camel_name = camel_case(&self.name); Rc::new(InstructionContent { name: self.name, camel_name, opcode_number, doc: self.doc, operands_in, operands_out, constraints: self.constraints.unwrap_or_else(Vec::new), format: self.format, polymorphic_info, value_opnums, value_results, imm_opnums, is_terminator: self.is_terminator, is_branch: self.is_branch, is_indirect_branch: self.is_indirect_branch, is_call: self.is_call, is_return: self.is_return, is_ghost: self.is_ghost, can_load: self.can_load, can_store: self.can_store, can_trap: self.can_trap, other_side_effects: self.other_side_effects, writes_cpu_flags, clobbers_all_regs: self.clobbers_all_regs, }) } } /// A thin wrapper like Option, but with more precise semantics. #[derive(Clone)] pub(crate) enum ValueTypeOrAny { ValueType(ValueType), Any, } impl ValueTypeOrAny { pub fn expect(self, msg: &str) -> ValueType { match self { ValueTypeOrAny::ValueType(vt) => vt, ValueTypeOrAny::Any => panic!("Unexpected Any: {}", msg), } } } /// The number of bits in the vector type VectorBitWidth = u64; /// An parameter used for binding instructions to specific types or values pub(crate) enum BindParameter { Any, Lane(LaneType), Vector(LaneType, VectorBitWidth), Reference(ReferenceType), Immediate(Immediate), } /// Constructor for more easily building vector parameters from any lane type pub(crate) fn vector(parameter: impl Into, vector_size: VectorBitWidth) -> BindParameter { BindParameter::Vector(parameter.into(), vector_size) } impl From for BindParameter { fn from(ty: Int) -> Self { BindParameter::Lane(ty.into()) } } impl From for BindParameter { fn from(ty: Bool) -> Self { BindParameter::Lane(ty.into()) } } impl From for BindParameter { fn from(ty: Float) -> Self { BindParameter::Lane(ty.into()) } } impl From for BindParameter { fn from(ty: LaneType) -> Self { BindParameter::Lane(ty) } } impl From for BindParameter { fn from(ty: Reference) -> Self { BindParameter::Reference(ty.into()) } } impl From for BindParameter { fn from(imm: Immediate) -> Self { BindParameter::Immediate(imm) } } #[derive(Clone)] pub(crate) enum Immediate { // When needed, this enum should be expanded to include other immediate types (e.g. u8, u128). IntCC(IntCC), } impl Display for Immediate { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { match self { Immediate::IntCC(x) => write!(f, "IntCC::{:?}", x), } } } #[derive(Clone)] pub(crate) struct BoundInstruction { pub inst: Instruction, pub value_types: Vec, pub immediate_values: Vec, } impl BoundInstruction { /// Construct a new bound instruction (with nothing bound yet) from an instruction fn new(inst: &Instruction) -> Self { BoundInstruction { inst: inst.clone(), value_types: vec![], immediate_values: vec![], } } /// Verify that the bindings for a BoundInstruction are correct. fn verify_bindings(&self) -> Result<(), String> { // Verify that binding types to the instruction does not violate the polymorphic rules. if !self.value_types.is_empty() { match &self.inst.polymorphic_info { Some(poly) => { if self.value_types.len() > 1 + poly.other_typevars.len() { return Err(format!( "trying to bind too many types for {}", self.inst.name )); } } None => { return Err(format!( "trying to bind a type for {} which is not a polymorphic instruction", self.inst.name )); } } } // Verify that only the right number of immediates are bound. let immediate_count = self .inst .operands_in .iter() .filter(|o| o.is_immediate_or_entityref()) .count(); if self.immediate_values.len() > immediate_count { return Err(format!( "trying to bind too many immediates ({}) to instruction {} which only expects {} \ immediates", self.immediate_values.len(), self.inst.name, immediate_count )); } Ok(()) } } impl Bindable for BoundInstruction { fn bind(&self, parameter: impl Into) -> BoundInstruction { let mut modified = self.clone(); match parameter.into() { BindParameter::Any => modified.value_types.push(ValueTypeOrAny::Any), BindParameter::Lane(lane_type) => modified .value_types .push(ValueTypeOrAny::ValueType(lane_type.into())), BindParameter::Vector(lane_type, vector_size_in_bits) => { let num_lanes = vector_size_in_bits / lane_type.lane_bits(); assert!( num_lanes >= 2, "Minimum lane number for bind_vector is 2, found {}.", num_lanes, ); let vector_type = ValueType::Vector(VectorType::new(lane_type, num_lanes)); modified .value_types .push(ValueTypeOrAny::ValueType(vector_type)); } BindParameter::Reference(reference_type) => { modified .value_types .push(ValueTypeOrAny::ValueType(reference_type.into())); } BindParameter::Immediate(immediate) => modified.immediate_values.push(immediate), } modified.verify_bindings().unwrap(); modified } } /// Checks that the input operands actually match the given format. fn verify_format(inst_name: &str, operands_in: &[Operand], format: &InstructionFormat) { // A format is defined by: // - its number of input value operands, // - its number and names of input immediate operands, // - whether it has a value list or not. let mut num_values = 0; let mut num_immediates = 0; for operand in operands_in.iter() { if operand.is_varargs() { assert!( format.has_value_list, "instruction {} has varargs, but its format {} doesn't have a value list; you may \ need to use a different format.", inst_name, format.name ); } if operand.is_value() { num_values += 1; } if operand.is_immediate_or_entityref() { if let Some(format_field) = format.imm_fields.get(num_immediates) { assert_eq!( format_field.kind.rust_field_name, operand.kind.rust_field_name, "{}th operand of {} should be {} (according to format), not {} (according to \ inst definition). You may need to use a different format.", num_immediates, inst_name, format_field.kind.rust_field_name, operand.kind.rust_field_name ); num_immediates += 1; } } } assert_eq!( num_values, format.num_value_operands, "inst {} doesn't have as many value input operands as its format {} declares; you may need \ to use a different format.", inst_name, format.name ); assert_eq!( num_immediates, format.imm_fields.len(), "inst {} doesn't have as many immediate input \ operands as its format {} declares; you may need to use a different format.", inst_name, format.name ); } /// Check if this instruction is polymorphic, and verify its use of type variables. fn verify_polymorphic( operands_in: &[Operand], operands_out: &[Operand], format: &InstructionFormat, value_opnums: &[usize], ) -> Option { // The instruction is polymorphic if it has one free input or output operand. let is_polymorphic = operands_in .iter() .any(|op| op.is_value() && op.type_var().unwrap().free_typevar().is_some()) || operands_out .iter() .any(|op| op.is_value() && op.type_var().unwrap().free_typevar().is_some()); if !is_polymorphic { return None; } // Verify the use of type variables. let tv_op = format.typevar_operand; let mut maybe_error_message = None; if let Some(tv_op) = tv_op { if tv_op < value_opnums.len() { let op_num = value_opnums[tv_op]; let tv = operands_in[op_num].type_var().unwrap(); let free_typevar = tv.free_typevar(); if (free_typevar.is_some() && tv == &free_typevar.unwrap()) || tv.singleton_type().is_some() { match is_ctrl_typevar_candidate(tv, &operands_in, &operands_out) { Ok(other_typevars) => { return Some(PolymorphicInfo { use_typevar_operand: true, ctrl_typevar: tv.clone(), other_typevars, }); } Err(error_message) => { maybe_error_message = Some(error_message); } } } } }; // If we reached here, it means the type variable indicated as the typevar operand couldn't // control every other input and output type variable. We need to look at the result type // variables. if operands_out.is_empty() { // No result means no other possible type variable, so it's a type inference failure. match maybe_error_message { Some(msg) => panic!("{}", msg), None => panic!("typevar_operand must be a free type variable"), } } // Otherwise, try to infer the controlling type variable by looking at the first result. let tv = operands_out[0].type_var().unwrap(); let free_typevar = tv.free_typevar(); if free_typevar.is_some() && tv != &free_typevar.unwrap() { panic!("first result must be a free type variable"); } // At this point, if the next unwrap() fails, it means the output type couldn't be used as a // controlling type variable either; panicking is the right behavior. let other_typevars = is_ctrl_typevar_candidate(tv, &operands_in, &operands_out).unwrap(); Some(PolymorphicInfo { use_typevar_operand: false, ctrl_typevar: tv.clone(), other_typevars, }) } /// Verify that the use of TypeVars is consistent with `ctrl_typevar` as the controlling type /// variable. /// /// All polymorhic inputs must either be derived from `ctrl_typevar` or be independent free type /// variables only used once. /// /// All polymorphic results must be derived from `ctrl_typevar`. /// /// Return a vector of other type variables used, or a string explaining what went wrong. fn is_ctrl_typevar_candidate( ctrl_typevar: &TypeVar, operands_in: &[Operand], operands_out: &[Operand], ) -> Result, String> { let mut other_typevars = Vec::new(); // Check value inputs. for input in operands_in { if !input.is_value() { continue; } let typ = input.type_var().unwrap(); let free_typevar = typ.free_typevar(); // Non-polymorphic or derived from ctrl_typevar is OK. if free_typevar.is_none() { continue; } let free_typevar = free_typevar.unwrap(); if &free_typevar == ctrl_typevar { continue; } // No other derived typevars allowed. if typ != &free_typevar { return Err(format!( "{:?}: type variable {} must be derived from {:?} while it is derived from {:?}", input, typ.name, ctrl_typevar, free_typevar )); } // Other free type variables can only be used once each. for other_tv in &other_typevars { if &free_typevar == other_tv { return Err(format!( "non-controlling type variable {} can't be used more than once", free_typevar.name )); } } other_typevars.push(free_typevar); } // Check outputs. for result in operands_out { if !result.is_value() { continue; } let typ = result.type_var().unwrap(); let free_typevar = typ.free_typevar(); // Non-polymorphic or derived from ctrl_typevar is OK. if free_typevar.is_none() || &free_typevar.unwrap() == ctrl_typevar { continue; } return Err("type variable in output not derived from ctrl_typevar".into()); } Ok(other_typevars) } #[derive(Clone, Hash, PartialEq, Eq)] pub(crate) enum FormatPredicateKind { /// Is the field member equal to the expected value (stored here)? IsEqual(String), /// Is the immediate instruction format field representable as an n-bit two's complement /// integer? (with width: first member, scale: second member). /// The predicate is true if the field is in the range: `-2^(width-1) -- 2^(width-1)-1` and a /// multiple of `2^scale`. IsSignedInt(usize, usize), /// Is the immediate instruction format field representable as an n-bit unsigned integer? (with /// width: first member, scale: second member). /// The predicate is true if the field is in the range: `0 -- 2^width - 1` and a multiple of /// `2^scale`. IsUnsignedInt(usize, usize), /// Is the immediate format field member an integer equal to zero? IsZeroInt, /// Is the immediate format field member equal to zero? (float32 version) IsZero32BitFloat, /// Is the immediate format field member equal to zero? (float64 version) IsZero64BitFloat, /// Is the immediate format field member equal zero in all lanes? IsAllZeroes, /// Does the immediate format field member have ones in all bits of all lanes? IsAllOnes, /// Has the value list (in member_name) the size specified in parameter? LengthEquals(usize), /// Is the referenced function colocated? IsColocatedFunc, /// Is the referenced data object colocated? IsColocatedData, } #[derive(Clone, Hash, PartialEq, Eq)] pub(crate) struct FormatPredicateNode { format_name: &'static str, member_name: &'static str, kind: FormatPredicateKind, } impl FormatPredicateNode { fn new( format: &InstructionFormat, field_name: &'static str, kind: FormatPredicateKind, ) -> Self { let member_name = format.imm_by_name(field_name).member; Self { format_name: format.name, member_name, kind, } } fn new_raw( format: &InstructionFormat, member_name: &'static str, kind: FormatPredicateKind, ) -> Self { Self { format_name: format.name, member_name, kind, } } fn destructuring_member_name(&self) -> &'static str { match &self.kind { FormatPredicateKind::LengthEquals(_) => { // Length operates on the argument value list. assert!(self.member_name == "args"); "ref args" } _ => self.member_name, } } fn rust_predicate(&self) -> String { match &self.kind { FormatPredicateKind::IsEqual(arg) => { format!("predicates::is_equal({}, {})", self.member_name, arg) } FormatPredicateKind::IsSignedInt(width, scale) => format!( "predicates::is_signed_int({}, {}, {})", self.member_name, width, scale ), FormatPredicateKind::IsUnsignedInt(width, scale) => format!( "predicates::is_unsigned_int({}, {}, {})", self.member_name, width, scale ), FormatPredicateKind::IsZeroInt => { format!("predicates::is_zero_int({})", self.member_name) } FormatPredicateKind::IsZero32BitFloat => { format!("predicates::is_zero_32_bit_float({})", self.member_name) } FormatPredicateKind::IsZero64BitFloat => { format!("predicates::is_zero_64_bit_float({})", self.member_name) } FormatPredicateKind::IsAllZeroes => format!( "predicates::is_all_zeroes(func.dfg.constants.get({}))", self.member_name ), FormatPredicateKind::IsAllOnes => format!( "predicates::is_all_ones(func.dfg.constants.get({}))", self.member_name ), FormatPredicateKind::LengthEquals(num) => format!( "predicates::has_length_of({}, {}, func)", self.member_name, num ), FormatPredicateKind::IsColocatedFunc => { format!("predicates::is_colocated_func({}, func)", self.member_name,) } FormatPredicateKind::IsColocatedData => { format!("predicates::is_colocated_data({}, func)", self.member_name) } } } } #[derive(Clone, Hash, PartialEq, Eq)] pub(crate) enum TypePredicateNode { /// Is the value argument (at the index designated by the first member) the same type as the /// type name (second member)? TypeVarCheck(usize, String), /// Is the controlling type variable the same type as the one designated by the type name /// (only member)? CtrlTypeVarCheck(String), } impl TypePredicateNode { fn rust_predicate(&self, func_str: &str) -> String { match self { TypePredicateNode::TypeVarCheck(index, value_type_name) => format!( "{}.dfg.value_type(args[{}]) == {}", func_str, index, value_type_name ), TypePredicateNode::CtrlTypeVarCheck(value_type_name) => { format!("{}.dfg.ctrl_typevar(inst) == {}", func_str, value_type_name) } } } } /// A basic node in an instruction predicate: either an atom, or an AND of two conditions. #[derive(Clone, Hash, PartialEq, Eq)] pub(crate) enum InstructionPredicateNode { FormatPredicate(FormatPredicateNode), TypePredicate(TypePredicateNode), /// An AND-combination of two or more other predicates. And(Vec), /// An OR-combination of two or more other predicates. Or(Vec), } impl InstructionPredicateNode { fn rust_predicate(&self, func_str: &str) -> String { match self { InstructionPredicateNode::FormatPredicate(node) => node.rust_predicate(), InstructionPredicateNode::TypePredicate(node) => node.rust_predicate(func_str), InstructionPredicateNode::And(nodes) => nodes .iter() .map(|x| x.rust_predicate(func_str)) .collect::>() .join(" && "), InstructionPredicateNode::Or(nodes) => nodes .iter() .map(|x| x.rust_predicate(func_str)) .collect::>() .join(" || "), } } pub fn format_destructuring_member_name(&self) -> &str { match self { InstructionPredicateNode::FormatPredicate(format_pred) => { format_pred.destructuring_member_name() } _ => panic!("Only for leaf format predicates"), } } pub fn format_name(&self) -> &str { match self { InstructionPredicateNode::FormatPredicate(format_pred) => format_pred.format_name, _ => panic!("Only for leaf format predicates"), } } pub fn is_type_predicate(&self) -> bool { match self { InstructionPredicateNode::FormatPredicate(_) | InstructionPredicateNode::And(_) | InstructionPredicateNode::Or(_) => false, InstructionPredicateNode::TypePredicate(_) => true, } } fn collect_leaves(&self) -> Vec<&InstructionPredicateNode> { let mut ret = Vec::new(); match self { InstructionPredicateNode::And(nodes) | InstructionPredicateNode::Or(nodes) => { for node in nodes { ret.extend(node.collect_leaves()); } } _ => ret.push(self), } ret } } #[derive(Clone, Hash, PartialEq, Eq)] pub(crate) struct InstructionPredicate { node: Option, } impl Into for InstructionPredicateNode { fn into(self) -> InstructionPredicate { InstructionPredicate { node: Some(self) } } } impl InstructionPredicate { pub fn new() -> Self { Self { node: None } } pub fn unwrap(self) -> InstructionPredicateNode { self.node.unwrap() } pub fn new_typevar_check( inst: &Instruction, type_var: &TypeVar, value_type: &ValueType, ) -> InstructionPredicateNode { let index = inst .value_opnums .iter() .enumerate() .find(|(_, &op_num)| inst.operands_in[op_num].type_var().unwrap() == type_var) .unwrap() .0; InstructionPredicateNode::TypePredicate(TypePredicateNode::TypeVarCheck( index, value_type.rust_name(), )) } pub fn new_ctrl_typevar_check(value_type: &ValueType) -> InstructionPredicateNode { InstructionPredicateNode::TypePredicate(TypePredicateNode::CtrlTypeVarCheck( value_type.rust_name(), )) } pub fn new_is_field_equal( format: &InstructionFormat, field_name: &'static str, imm_value: String, ) -> InstructionPredicateNode { InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new( format, field_name, FormatPredicateKind::IsEqual(imm_value), )) } /// Used only for the AST module, which directly passes in the format field. pub fn new_is_field_equal_ast( format: &InstructionFormat, field: &FormatField, imm_value: String, ) -> InstructionPredicateNode { InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new_raw( format, field.member, FormatPredicateKind::IsEqual(imm_value), )) } pub fn new_is_signed_int( format: &InstructionFormat, field_name: &'static str, width: usize, scale: usize, ) -> InstructionPredicateNode { InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new( format, field_name, FormatPredicateKind::IsSignedInt(width, scale), )) } pub fn new_is_unsigned_int( format: &InstructionFormat, field_name: &'static str, width: usize, scale: usize, ) -> InstructionPredicateNode { InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new( format, field_name, FormatPredicateKind::IsUnsignedInt(width, scale), )) } pub fn new_is_zero_int( format: &InstructionFormat, field_name: &'static str, ) -> InstructionPredicateNode { InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new( format, field_name, FormatPredicateKind::IsZeroInt, )) } pub fn new_is_zero_32bit_float( format: &InstructionFormat, field_name: &'static str, ) -> InstructionPredicateNode { InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new( format, field_name, FormatPredicateKind::IsZero32BitFloat, )) } pub fn new_is_zero_64bit_float( format: &InstructionFormat, field_name: &'static str, ) -> InstructionPredicateNode { InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new( format, field_name, FormatPredicateKind::IsZero64BitFloat, )) } pub fn new_is_all_zeroes( format: &InstructionFormat, field_name: &'static str, ) -> InstructionPredicateNode { InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new( format, field_name, FormatPredicateKind::IsAllZeroes, )) } pub fn new_is_all_ones( format: &InstructionFormat, field_name: &'static str, ) -> InstructionPredicateNode { InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new( format, field_name, FormatPredicateKind::IsAllOnes, )) } pub fn new_length_equals(format: &InstructionFormat, size: usize) -> InstructionPredicateNode { assert!( format.has_value_list, "the format must be variadic in number of arguments" ); InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new_raw( format, "args", FormatPredicateKind::LengthEquals(size), )) } pub fn new_is_colocated_func( format: &InstructionFormat, field_name: &'static str, ) -> InstructionPredicateNode { InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new( format, field_name, FormatPredicateKind::IsColocatedFunc, )) } pub fn new_is_colocated_data(formats: &Formats) -> InstructionPredicateNode { let format = &formats.unary_global_value; InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new( &*format, "global_value", FormatPredicateKind::IsColocatedData, )) } pub fn and(mut self, new_node: InstructionPredicateNode) -> Self { let node = self.node; let mut and_nodes = match node { Some(node) => match node { InstructionPredicateNode::And(nodes) => nodes, InstructionPredicateNode::Or(_) => { panic!("Can't mix and/or without implementing operator precedence!") } _ => vec![node], }, _ => Vec::new(), }; and_nodes.push(new_node); self.node = Some(InstructionPredicateNode::And(and_nodes)); self } pub fn or(mut self, new_node: InstructionPredicateNode) -> Self { let node = self.node; let mut or_nodes = match node { Some(node) => match node { InstructionPredicateNode::Or(nodes) => nodes, InstructionPredicateNode::And(_) => { panic!("Can't mix and/or without implementing operator precedence!") } _ => vec![node], }, _ => Vec::new(), }; or_nodes.push(new_node); self.node = Some(InstructionPredicateNode::Or(or_nodes)); self } pub fn rust_predicate(&self, func_str: &str) -> Option { self.node.as_ref().map(|root| root.rust_predicate(func_str)) } /// Returns the type predicate if this is one, or None otherwise. pub fn type_predicate(&self, func_str: &str) -> Option { let node = self.node.as_ref().unwrap(); if node.is_type_predicate() { Some(node.rust_predicate(func_str)) } else { None } } /// Returns references to all the nodes that are leaves in the condition (i.e. by flattening /// AND/OR). pub fn collect_leaves(&self) -> Vec<&InstructionPredicateNode> { self.node.as_ref().unwrap().collect_leaves() } } #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub(crate) struct InstructionPredicateNumber(u32); entity_impl!(InstructionPredicateNumber); pub(crate) type InstructionPredicateMap = PrimaryMap; /// A registry of predicates to help deduplicating them, during Encodings construction. When the /// construction process is over, it needs to be extracted with `extract` and associated to the /// TargetIsa. pub(crate) struct InstructionPredicateRegistry { /// Maps a predicate number to its actual predicate. map: InstructionPredicateMap, /// Inverse map: maps a predicate to its predicate number. This is used before inserting a /// predicate, to check whether it already exists. inverted_map: HashMap, } impl InstructionPredicateRegistry { pub fn new() -> Self { Self { map: PrimaryMap::new(), inverted_map: HashMap::new(), } } pub fn insert(&mut self, predicate: InstructionPredicate) -> InstructionPredicateNumber { match self.inverted_map.get(&predicate) { Some(&found) => found, None => { let key = self.map.push(predicate.clone()); self.inverted_map.insert(predicate, key); key } } } pub fn extract(self) -> InstructionPredicateMap { self.map } } /// An instruction specification, containing an instruction that has bound types or not. pub(crate) enum InstSpec { Inst(Instruction), Bound(BoundInstruction), } impl InstSpec { pub fn inst(&self) -> &Instruction { match &self { InstSpec::Inst(inst) => inst, InstSpec::Bound(bound_inst) => &bound_inst.inst, } } } impl Bindable for InstSpec { fn bind(&self, parameter: impl Into) -> BoundInstruction { match self { InstSpec::Inst(inst) => inst.bind(parameter.into()), InstSpec::Bound(inst) => inst.bind(parameter.into()), } } } impl Into for &Instruction { fn into(self) -> InstSpec { InstSpec::Inst(self.clone()) } } impl Into for BoundInstruction { fn into(self) -> InstSpec { InstSpec::Bound(self) } } #[cfg(test)] mod test { use super::*; use crate::cdsl::formats::InstructionFormatBuilder; use crate::cdsl::operands::{OperandKind, OperandKindFields}; use crate::cdsl::typevar::TypeSetBuilder; use crate::shared::types::Int::{I32, I64}; fn field_to_operand(index: usize, field: OperandKindFields) -> Operand { // Pretend the index string is &'static. let name = Box::leak(index.to_string().into_boxed_str()); // Format's name / rust_type don't matter here. let kind = OperandKind::new(name, name, field); let operand = Operand::new(name, kind); operand } fn field_to_operands(types: Vec) -> Vec { types .iter() .enumerate() .map(|(i, f)| field_to_operand(i, f.clone())) .collect() } fn build_fake_instruction( inputs: Vec, outputs: Vec, ) -> Instruction { // Setup a format from the input operands. let mut format = InstructionFormatBuilder::new("fake"); for (i, f) in inputs.iter().enumerate() { match f { OperandKindFields::TypeVar(_) => format = format.value(), OperandKindFields::ImmValue => { format = format.imm(&field_to_operand(i, f.clone()).kind) } _ => {} }; } let format = format.build(); // Create the fake instruction. InstructionBuilder::new("fake", "A fake instruction for testing.", &format) .operands_in(field_to_operands(inputs).iter().collect()) .operands_out(field_to_operands(outputs).iter().collect()) .build(OpcodeNumber(42)) } #[test] fn ensure_bound_instructions_can_bind_lane_types() { let type1 = TypeSetBuilder::new().ints(8..64).build(); let in1 = OperandKindFields::TypeVar(TypeVar::new("a", "...", type1)); let inst = build_fake_instruction(vec![in1], vec![]); inst.bind(LaneType::Int(I32)); } #[test] fn ensure_bound_instructions_can_bind_immediates() { let inst = build_fake_instruction(vec![OperandKindFields::ImmValue], vec![]); let bound_inst = inst.bind(Immediate::IntCC(IntCC::Equal)); assert!(bound_inst.verify_bindings().is_ok()); } #[test] #[should_panic] fn ensure_instructions_fail_to_bind() { let inst = build_fake_instruction(vec![], vec![]); inst.bind(BindParameter::Lane(LaneType::Int(I32))); // Trying to bind to an instruction with no inputs should fail. } #[test] #[should_panic] fn ensure_bound_instructions_fail_to_bind_too_many_types() { let type1 = TypeSetBuilder::new().ints(8..64).build(); let in1 = OperandKindFields::TypeVar(TypeVar::new("a", "...", type1)); let inst = build_fake_instruction(vec![in1], vec![]); inst.bind(LaneType::Int(I32)).bind(LaneType::Int(I64)); } #[test] #[should_panic] fn ensure_instructions_fail_to_bind_too_many_immediates() { let inst = build_fake_instruction(vec![OperandKindFields::ImmValue], vec![]); inst.bind(BindParameter::Immediate(Immediate::IntCC(IntCC::Equal))) .bind(BindParameter::Immediate(Immediate::IntCC(IntCC::Equal))); // Trying to bind too many immediates to an instruction should fail; note that the immediate // values are nonsensical but irrelevant to the purpose of this test. } }