| //! Glue for working with `peepmatic`-generated peephole optimizers. |
| |
| use crate::cursor::{Cursor, FuncCursor}; |
| use crate::ir::{ |
| dfg::DataFlowGraph, |
| entities::{Inst, Value}, |
| immediates::{Imm64, Uimm64}, |
| instructions::{InstructionData, Opcode}, |
| types, InstBuilder, |
| }; |
| use crate::isa::TargetIsa; |
| use cranelift_codegen_shared::condcodes::IntCC; |
| use peepmatic_runtime::{ |
| cc::ConditionCode, |
| instruction_set::InstructionSet, |
| part::{Constant, Part}, |
| r#type::{BitWidth, Kind, Type}, |
| PeepholeOptimizations, PeepholeOptimizer, |
| }; |
| use std::borrow::Cow; |
| use std::boxed::Box; |
| use std::convert::{TryFrom, TryInto}; |
| use std::iter; |
| use std::ptr; |
| use std::sync::atomic::{AtomicPtr, Ordering}; |
| |
| peepmatic_traits::define_parse_and_typing_rules_for_operator! { |
| Opcode { |
| adjust_sp_down => AdjustSpDown { |
| parameters(iNN); |
| result(void); |
| } |
| adjust_sp_down_imm => AdjustSpDownImm { |
| immediates(iNN); |
| result(void); |
| } |
| band => Band { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| band_imm => BandImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| bconst => Bconst { |
| immediates(b1); |
| result(bNN); |
| } |
| bint => Bint { |
| parameters(bNN); |
| result(iNN); |
| } |
| bor => Bor { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| bor_imm => BorImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| brnz => Brnz { |
| parameters(bool_or_int); |
| result(void); |
| } |
| brz => Brz { |
| parameters(bool_or_int); |
| result(void); |
| } |
| bxor => Bxor { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| bxor_imm => BxorImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| iadd => Iadd { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| iadd_imm => IaddImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| icmp => Icmp { |
| immediates(cc); |
| parameters(iNN, iNN); |
| result(b1); |
| } |
| icmp_imm => IcmpImm { |
| immediates(cc, iNN); |
| parameters(iNN); |
| result(b1); |
| } |
| iconst => Iconst { |
| immediates(iNN); |
| result(iNN); |
| } |
| ifcmp => Ifcmp { |
| parameters(iNN, iNN); |
| result(cpu_flags); |
| } |
| ifcmp_imm => IfcmpImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(cpu_flags); |
| } |
| imul => Imul { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| imul_imm => ImulImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| ireduce => Ireduce { |
| parameters(iNN); |
| result(iMM); |
| is_reduce(true); |
| } |
| irsub_imm => IrsubImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| ishl => Ishl { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| ishl_imm => IshlImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| isub => Isub { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| rotl => Rotl { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| rotl_imm => RotlImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| rotr => Rotr { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| rotr_imm => RotrImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| sdiv => Sdiv { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| sdiv_imm => SdivImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| select => Select { |
| parameters(bool_or_int, any_t, any_t); |
| result(any_t); |
| } |
| sextend => Sextend { |
| parameters(iNN); |
| result(iMM); |
| is_extend(true); |
| } |
| srem => Srem { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| srem_imm => SremImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| sshr => Sshr { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| sshr_imm => SshrImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| trapnz => Trapnz { |
| parameters(bool_or_int); |
| result(void); |
| } |
| trapz => Trapz { |
| parameters(bool_or_int); |
| result(void); |
| } |
| udiv => Udiv { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| udiv_imm => UdivImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| uextend => Uextend { |
| parameters(iNN); |
| result(iMM); |
| is_extend(true); |
| } |
| urem => Urem { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| urem_imm => UremImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| ushr => Ushr { |
| parameters(iNN, iNN); |
| result(iNN); |
| } |
| ushr_imm => UshrImm { |
| immediates(iNN); |
| parameters(iNN); |
| result(iNN); |
| } |
| } |
| parse_cfg(feature = "rebuild-peephole-optimizers"); |
| } |
| |
| /// Code required to rebuild Peepmatic-based peephole optimizers. |
| /// |
| /// This module is used to scope imports and dependencies that are only required |
| /// for building peephole optimizers (as opposed to just using pre-built |
| /// peephole optimizers). This helps ensure that our regular builds using |
| /// pre-built peephole optimizers stay lean. |
| #[cfg(feature = "rebuild-peephole-optimizers")] |
| mod rebuild { |
| use super::*; |
| use alloc::vec::Vec; |
| use std::fs; |
| use std::path::Path; |
| |
| /// Rebuild the `preopt.peepmatic` peephole optimizer. |
| /// |
| /// Saves and overwrites the old `preopt.serialized` build and returns a |
| /// copy of the result. |
| pub fn rebuild_preopt() -> Vec<u8> { |
| let codegen_path = Path::new(include_str!(concat!( |
| env!("OUT_DIR"), |
| "/CRANELIFT_CODEGEN_PATH" |
| ))); |
| let source_path = codegen_path.join("src").join("preopt.peepmatic"); |
| |
| let preopt = peepmatic::compile_file::<Opcode>(&source_path) |
| .expect("failed to compile `src/preopt.peepmatic`"); |
| |
| let serialized_path = codegen_path.join("src").join("preopt.serialized"); |
| preopt |
| .serialize_to_file(&serialized_path) |
| .expect("failed to serialize peephole optimizer to `src/preopt.serialized`"); |
| fs::read(&serialized_path).expect("failed to read `src/preopt.serialized`") |
| } |
| } |
| |
| /// Get the `preopt.peepmatic` peephole optimizer. |
| pub(crate) fn preopt<'a, 'b>( |
| isa: &'b dyn TargetIsa, |
| ) -> PeepholeOptimizer<'static, 'a, &'b dyn TargetIsa> { |
| #[cfg(feature = "rebuild-peephole-optimizers")] |
| fn get_serialized() -> Cow<'static, [u8]> { |
| rebuild::rebuild_preopt().into() |
| } |
| |
| #[cfg(not(feature = "rebuild-peephole-optimizers"))] |
| fn get_serialized() -> Cow<'static, [u8]> { |
| static SERIALIZED: &[u8] = include_bytes!("preopt.serialized"); |
| SERIALIZED.into() |
| } |
| |
| // Once initialized, this must never be re-assigned. The initialized value |
| // is semantically "static data" and is intentionally leaked for the whole |
| // program's lifetime. |
| static DESERIALIZED: AtomicPtr<PeepholeOptimizations<Opcode>> = AtomicPtr::new(ptr::null_mut()); |
| |
| // If `DESERIALIZED` has already been initialized, then just use it. |
| let ptr = DESERIALIZED.load(Ordering::SeqCst); |
| if let Some(peep_opts) = unsafe { ptr.as_ref() } { |
| return peep_opts.optimizer(isa); |
| } |
| |
| // Otherwise, if `DESERIALIZED` hasn't been initialized, then we need to |
| // deserialize the peephole optimizations and initialize it. However, |
| // another thread could be doing the same thing concurrently, so there is a |
| // race to see who initializes `DESERIALIZED` first, and we need to be |
| // prepared to both win or lose that race. |
| let peep_opts = PeepholeOptimizations::deserialize(&get_serialized()) |
| .expect("should always be able to deserialize `preopt.serialized`"); |
| let peep_opts = Box::into_raw(Box::new(peep_opts)); |
| |
| // Only update `DESERIALIZE` if it is still null, attempting to perform the |
| // one-time transition from null -> non-null. |
| if DESERIALIZED |
| .compare_and_swap(ptr::null_mut(), peep_opts, Ordering::SeqCst) |
| .is_null() |
| { |
| // We won the race to initialize `DESERIALIZED`. |
| debug_assert_eq!(DESERIALIZED.load(Ordering::SeqCst), peep_opts); |
| let peep_opts = unsafe { &*peep_opts }; |
| return peep_opts.optimizer(isa); |
| } |
| |
| // We lost the race to initialize `DESERIALIZED`. Drop our no-longer-needed |
| // instance of `peep_opts` and get the pointer to the instance that won the |
| // race. |
| let _ = unsafe { Box::from_raw(peep_opts) }; |
| let peep_opts = DESERIALIZED.load(Ordering::SeqCst); |
| let peep_opts = unsafe { peep_opts.as_ref().unwrap() }; |
| peep_opts.optimizer(isa) |
| } |
| |
| /// Either a `Value` or an `Inst`. |
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
| pub enum ValueOrInst { |
| Value(Value), |
| Inst(Inst), |
| } |
| |
| impl ValueOrInst { |
| /// Get the underlying `Value` if any. |
| pub fn value(&self) -> Option<Value> { |
| match *self { |
| Self::Value(v) => Some(v), |
| Self::Inst(_) => None, |
| } |
| } |
| |
| /// Get the underlying `Inst` if any. |
| pub fn inst(&self) -> Option<Inst> { |
| match *self { |
| Self::Inst(i) => Some(i), |
| Self::Value(_) => None, |
| } |
| } |
| |
| /// Unwrap the underlying `Value`, panicking if it is not a `Value. |
| pub fn unwrap_value(&self) -> Value { |
| self.value().unwrap() |
| } |
| |
| /// Unwrap the underlying `Inst`, panicking if it is not a `Inst. |
| pub fn unwrap_inst(&self) -> Inst { |
| self.inst().unwrap() |
| } |
| |
| /// Is this a `Value`? |
| pub fn is_value(&self) -> bool { |
| self.value().is_some() |
| } |
| |
| /// Is this an `Inst`? |
| pub fn is_inst(&self) -> bool { |
| self.inst().is_some() |
| } |
| |
| fn resolve_inst(&self, dfg: &DataFlowGraph) -> Option<Inst> { |
| match *self { |
| ValueOrInst::Inst(i) => Some(i), |
| ValueOrInst::Value(v) => dfg.value_def(v).inst(), |
| } |
| } |
| |
| fn result_bit_width(&self, dfg: &DataFlowGraph) -> u8 { |
| match *self { |
| ValueOrInst::Value(v) => dfg.value_type(v).bits().try_into().unwrap(), |
| ValueOrInst::Inst(inst) => { |
| let result = dfg.first_result(inst); |
| dfg.value_type(result).bits().try_into().unwrap() |
| } |
| } |
| } |
| |
| fn to_constant(&self, pos: &mut FuncCursor) -> Option<Constant> { |
| let inst = self.resolve_inst(&pos.func.dfg)?; |
| match pos.func.dfg[inst] { |
| InstructionData::UnaryImm { |
| opcode: Opcode::Iconst, |
| imm, |
| } => { |
| let width = self.result_bit_width(&pos.func.dfg).try_into().unwrap(); |
| let x: i64 = imm.into(); |
| Some(Constant::Int(x as u64, width)) |
| } |
| InstructionData::UnaryBool { |
| opcode: Opcode::Bconst, |
| imm, |
| } => { |
| let width = self.result_bit_width(&pos.func.dfg).try_into().unwrap(); |
| Some(Constant::Bool(imm, width)) |
| } |
| _ => None, |
| } |
| } |
| } |
| |
| impl From<Value> for ValueOrInst { |
| fn from(v: Value) -> ValueOrInst { |
| ValueOrInst::Value(v) |
| } |
| } |
| |
| impl From<Inst> for ValueOrInst { |
| fn from(i: Inst) -> ValueOrInst { |
| ValueOrInst::Inst(i) |
| } |
| } |
| |
| /// Get the fixed bit width of `bit_width`, or if it is polymorphic, the bit |
| /// width of `root`. |
| fn bit_width(dfg: &DataFlowGraph, bit_width: BitWidth, root: Inst) -> u8 { |
| bit_width.fixed_width().unwrap_or_else(|| { |
| let tyvar = dfg.ctrl_typevar(root); |
| let ty = dfg.compute_result_type(root, 0, tyvar).unwrap(); |
| u8::try_from(ty.bits()).unwrap() |
| }) |
| } |
| |
| /// Convert the constant `c` into an instruction. |
| fn const_to_value<'a>(builder: impl InstBuilder<'a>, c: Constant, root: Inst) -> Value { |
| match c { |
| Constant::Bool(b, width) => { |
| let width = bit_width(builder.data_flow_graph(), width, root); |
| let ty = match width { |
| 1 => types::B1, |
| 8 => types::B8, |
| 16 => types::B16, |
| 32 => types::B32, |
| 64 => types::B64, |
| 128 => types::B128, |
| _ => unreachable!(), |
| }; |
| builder.bconst(ty, b) |
| } |
| Constant::Int(x, width) => { |
| let width = bit_width(builder.data_flow_graph(), width, root); |
| let ty = match width { |
| 8 => types::I8, |
| 16 => types::I16, |
| 32 => types::I32, |
| 64 => types::I64, |
| 128 => types::I128, |
| _ => unreachable!(), |
| }; |
| builder.iconst(ty, x as i64) |
| } |
| } |
| } |
| |
| fn part_to_value(pos: &mut FuncCursor, root: Inst, part: Part<ValueOrInst>) -> Option<Value> { |
| match part { |
| Part::Instruction(ValueOrInst::Inst(inst)) => { |
| pos.func.dfg.inst_results(inst).first().copied() |
| } |
| Part::Instruction(ValueOrInst::Value(v)) => Some(v), |
| Part::Constant(c) => Some(const_to_value(pos.ins(), c, root)), |
| Part::ConditionCode(_) => None, |
| } |
| } |
| |
| impl TryFrom<Constant> for Imm64 { |
| type Error = &'static str; |
| |
| fn try_from(c: Constant) -> Result<Self, Self::Error> { |
| match c { |
| Constant::Int(x, _) => Ok(Imm64::from(x as i64)), |
| Constant::Bool(..) => Err("cannot create Imm64 from Constant::Bool"), |
| } |
| } |
| } |
| |
| impl Into<Constant> for Imm64 { |
| #[inline] |
| fn into(self) -> Constant { |
| let x: i64 = self.into(); |
| Constant::Int(x as _, BitWidth::SixtyFour) |
| } |
| } |
| |
| impl Into<Part<ValueOrInst>> for Imm64 { |
| #[inline] |
| fn into(self) -> Part<ValueOrInst> { |
| let c: Constant = self.into(); |
| c.into() |
| } |
| } |
| |
| fn part_to_imm64(pos: &mut FuncCursor, part: Part<ValueOrInst>) -> Imm64 { |
| return match part { |
| Part::Instruction(x) => match x.to_constant(pos).unwrap_or_else(|| cannot_convert()) { |
| Constant::Int(x, _) => (x as i64).into(), |
| Constant::Bool(..) => cannot_convert(), |
| }, |
| Part::Constant(Constant::Int(x, _)) => (x as i64).into(), |
| Part::ConditionCode(_) | Part::Constant(Constant::Bool(..)) => cannot_convert(), |
| }; |
| |
| #[inline(never)] |
| #[cold] |
| fn cannot_convert() -> ! { |
| panic!("cannot convert part into `Imm64`") |
| } |
| } |
| |
| impl Into<Constant> for Uimm64 { |
| #[inline] |
| fn into(self) -> Constant { |
| let x: u64 = self.into(); |
| Constant::Int(x, BitWidth::SixtyFour) |
| } |
| } |
| |
| impl Into<Part<ValueOrInst>> for Uimm64 { |
| #[inline] |
| fn into(self) -> Part<ValueOrInst> { |
| let c: Constant = self.into(); |
| c.into() |
| } |
| } |
| |
| fn peepmatic_to_intcc(cc: ConditionCode) -> IntCC { |
| match cc { |
| ConditionCode::Eq => IntCC::Equal, |
| ConditionCode::Ne => IntCC::NotEqual, |
| ConditionCode::Slt => IntCC::SignedLessThan, |
| ConditionCode::Sle => IntCC::SignedGreaterThanOrEqual, |
| ConditionCode::Sgt => IntCC::SignedGreaterThan, |
| ConditionCode::Sge => IntCC::SignedLessThanOrEqual, |
| ConditionCode::Ult => IntCC::UnsignedLessThan, |
| ConditionCode::Uge => IntCC::UnsignedGreaterThanOrEqual, |
| ConditionCode::Ugt => IntCC::UnsignedGreaterThan, |
| ConditionCode::Ule => IntCC::UnsignedLessThanOrEqual, |
| ConditionCode::Of => IntCC::Overflow, |
| ConditionCode::Nof => IntCC::NotOverflow, |
| } |
| } |
| |
| fn intcc_to_peepmatic(cc: IntCC) -> ConditionCode { |
| match cc { |
| IntCC::Equal => ConditionCode::Eq, |
| IntCC::NotEqual => ConditionCode::Ne, |
| IntCC::SignedLessThan => ConditionCode::Slt, |
| IntCC::SignedGreaterThanOrEqual => ConditionCode::Sle, |
| IntCC::SignedGreaterThan => ConditionCode::Sgt, |
| IntCC::SignedLessThanOrEqual => ConditionCode::Sge, |
| IntCC::UnsignedLessThan => ConditionCode::Ult, |
| IntCC::UnsignedGreaterThanOrEqual => ConditionCode::Uge, |
| IntCC::UnsignedGreaterThan => ConditionCode::Ugt, |
| IntCC::UnsignedLessThanOrEqual => ConditionCode::Ule, |
| IntCC::Overflow => ConditionCode::Of, |
| IntCC::NotOverflow => ConditionCode::Nof, |
| } |
| } |
| |
| fn peepmatic_ty_to_ir_ty(ty: Type, dfg: &DataFlowGraph, root: Inst) -> types::Type { |
| match (ty.kind, bit_width(dfg, ty.bit_width, root)) { |
| (Kind::Int, 8) => types::I8, |
| (Kind::Int, 16) => types::I16, |
| (Kind::Int, 32) => types::I32, |
| (Kind::Int, 64) => types::I64, |
| (Kind::Int, 128) => types::I128, |
| (Kind::Bool, 1) => types::B1, |
| (Kind::Bool, 8) => types::I8, |
| (Kind::Bool, 16) => types::I16, |
| (Kind::Bool, 32) => types::I32, |
| (Kind::Bool, 64) => types::I64, |
| (Kind::Bool, 128) => types::I128, |
| _ => unreachable!(), |
| } |
| } |
| |
| // NB: the unsafe contract we must uphold here is that our implementation of |
| // `instruction_result_bit_width` must always return a valid, non-zero bit |
| // width. |
| unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa { |
| type Context = FuncCursor<'b>; |
| |
| type Operator = Opcode; |
| |
| type Instruction = ValueOrInst; |
| |
| fn replace_instruction( |
| &self, |
| pos: &mut FuncCursor<'b>, |
| old: ValueOrInst, |
| new: Part<ValueOrInst>, |
| ) -> ValueOrInst { |
| log::trace!("replace {:?} with {:?}", old, new); |
| let old_inst = old.resolve_inst(&pos.func.dfg).unwrap(); |
| |
| // Try to convert `new` to an instruction, because we prefer replacing |
| // an old instruction with a new one wholesale. However, if the |
| // replacement cannot be converted to an instruction (e.g. the |
| // right-hand side is a block/function parameter value) then we change |
| // the old instruction's result to an alias of the new value. |
| let new_inst = match new { |
| Part::Instruction(ValueOrInst::Inst(inst)) => Some(inst), |
| Part::Instruction(ValueOrInst::Value(_)) => { |
| // Do not try and follow the value definition. If we transplant |
| // this value's instruction, and there are other uses of this |
| // value, then we could mess up ordering between instructions. |
| None |
| } |
| Part::Constant(c) => { |
| let v = const_to_value(pos.ins(), c, old_inst); |
| let inst = pos.func.dfg.value_def(v).unwrap_inst(); |
| Some(inst) |
| } |
| Part::ConditionCode(_) => None, |
| }; |
| |
| match new_inst { |
| Some(new_inst) => { |
| pos.func.transplant_inst(old_inst, new_inst); |
| debug_assert_eq!(pos.current_inst(), Some(old_inst)); |
| old_inst.into() |
| } |
| None => { |
| let new_value = part_to_value(pos, old_inst, new).unwrap(); |
| |
| let old_results = pos.func.dfg.detach_results(old_inst); |
| let old_results = old_results.as_slice(&pos.func.dfg.value_lists); |
| assert_eq!(old_results.len(), 1); |
| let old_value = old_results[0]; |
| |
| pos.func.dfg.change_to_alias(old_value, new_value); |
| pos.func.dfg.replace(old_inst).nop(); |
| |
| new_value.into() |
| } |
| } |
| } |
| |
| fn operator<E>( |
| &self, |
| pos: &mut FuncCursor<'b>, |
| value_or_inst: ValueOrInst, |
| operands: &mut E, |
| ) -> Option<Opcode> |
| where |
| E: Extend<Part<Self::Instruction>>, |
| { |
| let inst = value_or_inst.resolve_inst(&pos.func.dfg)?; |
| Some(match pos.func.dfg[inst] { |
| InstructionData::Binary { |
| opcode: opcode @ Opcode::Band, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Bor, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Bxor, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Iadd, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Ifcmp, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Imul, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Ishl, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Isub, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Rotl, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Rotr, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Sdiv, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Srem, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Sshr, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Udiv, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Urem, |
| args, |
| } |
| | InstructionData::Binary { |
| opcode: opcode @ Opcode::Ushr, |
| args, |
| } => { |
| operands.extend(args.iter().map(|v| Part::Instruction((*v).into()))); |
| opcode |
| } |
| |
| InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::BandImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::BorImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::BxorImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::IaddImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::IfcmpImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::ImulImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::IrsubImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::IshlImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::RotlImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::RotrImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::SdivImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::SremImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::SshrImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::UdivImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::UremImm, |
| imm, |
| arg, |
| } |
| | InstructionData::BinaryImm64 { |
| opcode: opcode @ Opcode::UshrImm, |
| imm, |
| arg, |
| } => { |
| operands.extend( |
| iter::once(imm.into()).chain(iter::once(Part::Instruction(arg.into()))), |
| ); |
| opcode |
| } |
| |
| InstructionData::Branch { |
| opcode: opcode @ Opcode::Brnz, |
| ref args, |
| destination: _, |
| } |
| | InstructionData::Branch { |
| opcode: opcode @ Opcode::Brz, |
| ref args, |
| destination: _, |
| } => { |
| operands.extend( |
| args.as_slice(&pos.func.dfg.value_lists) |
| .iter() |
| .map(|v| Part::Instruction((*v).into())) |
| // NB: Peepmatic only knows about the condition, not any |
| // of the arguments to the block, which are special |
| // cased elsewhere, if/when we actually replace the |
| // instruction. |
| .take(1), |
| ); |
| opcode |
| } |
| |
| InstructionData::CondTrap { |
| opcode: opcode @ Opcode::Trapnz, |
| arg, |
| code: _, |
| } |
| | InstructionData::CondTrap { |
| opcode: opcode @ Opcode::Trapz, |
| arg, |
| code: _, |
| } => { |
| operands.extend(iter::once(Part::Instruction(arg.into()))); |
| opcode |
| } |
| |
| InstructionData::IntCompare { |
| opcode: opcode @ Opcode::Icmp, |
| cond, |
| args, |
| } => { |
| operands.extend( |
| iter::once(intcc_to_peepmatic(cond).into()) |
| .chain(args.iter().map(|v| Part::Instruction((*v).into()))), |
| ); |
| opcode |
| } |
| |
| InstructionData::IntCompareImm { |
| opcode: opcode @ Opcode::IcmpImm, |
| cond, |
| imm, |
| arg, |
| } => { |
| operands.extend( |
| iter::once(intcc_to_peepmatic(cond).into()) |
| .chain(iter::once(Part::Constant(imm.into()))) |
| .chain(iter::once(Part::Instruction(arg.into()))), |
| ); |
| opcode |
| } |
| |
| InstructionData::Ternary { |
| opcode: opcode @ Opcode::Select, |
| ref args, |
| } => { |
| operands.extend(args.iter().map(|v| Part::Instruction((*v).into()))); |
| opcode |
| } |
| |
| InstructionData::Unary { |
| opcode: opcode @ Opcode::AdjustSpDown, |
| arg, |
| } |
| | InstructionData::Unary { |
| opcode: opcode @ Opcode::Bint, |
| arg, |
| } |
| | InstructionData::Unary { |
| opcode: opcode @ Opcode::Ireduce, |
| arg, |
| } |
| | InstructionData::Unary { |
| opcode: opcode @ Opcode::Sextend, |
| arg, |
| } |
| | InstructionData::Unary { |
| opcode: opcode @ Opcode::Uextend, |
| arg, |
| } => { |
| operands.extend(iter::once(Part::Instruction(arg.into()))); |
| opcode |
| } |
| |
| InstructionData::UnaryBool { opcode, imm } => { |
| operands.extend(iter::once(Part::Constant(Constant::Bool( |
| imm, |
| BitWidth::Polymorphic, |
| )))); |
| opcode |
| } |
| |
| InstructionData::UnaryImm { |
| opcode: opcode @ Opcode::AdjustSpDownImm, |
| imm, |
| } |
| | InstructionData::UnaryImm { |
| opcode: opcode @ Opcode::Iconst, |
| imm, |
| } => { |
| operands.extend(iter::once(imm.into())); |
| opcode |
| } |
| ref otherwise => { |
| log::trace!("Not supported by Peepmatic: {:?}", otherwise); |
| return None; |
| } |
| }) |
| } |
| |
| fn make_inst_1( |
| &self, |
| pos: &mut FuncCursor<'b>, |
| root: ValueOrInst, |
| operator: Opcode, |
| r#type: Type, |
| a: Part<ValueOrInst>, |
| ) -> ValueOrInst { |
| log::trace!("make_inst_1: {:?}({:?})", operator, a); |
| |
| let root = root.resolve_inst(&pos.func.dfg).unwrap(); |
| match operator { |
| Opcode::AdjustSpDown => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| pos.ins().adjust_sp_down(a).into() |
| } |
| Opcode::AdjustSpDownImm => { |
| let c = a.unwrap_constant(); |
| let imm = Imm64::try_from(c).unwrap(); |
| pos.ins().adjust_sp_down_imm(imm).into() |
| } |
| Opcode::Bconst => { |
| let c = a.unwrap_constant(); |
| let val = const_to_value(pos.ins(), c, root); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Bint => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root); |
| let val = pos.ins().bint(ty, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Bnot => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let val = pos.ins().bnot(a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Brnz => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| |
| // NB: branching instructions must be the root of an |
| // optimization's right-hand side, so we get the destination |
| // block and arguments from the left-hand side's root. Peepmatic |
| // doesn't currently represent labels or varargs. |
| let block = pos.func.dfg[root].branch_destination().unwrap(); |
| let args = pos.func.dfg.inst_args(root)[1..].to_vec(); |
| |
| pos.ins().brnz(a, block, &args).into() |
| } |
| Opcode::Brz => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| |
| // See the comment in the `Opcode::Brnz` match argm. |
| let block = pos.func.dfg[root].branch_destination().unwrap(); |
| let args = pos.func.dfg.inst_args(root)[1..].to_vec(); |
| |
| pos.ins().brz(a, block, &args).into() |
| } |
| Opcode::Iconst => { |
| let a = a.unwrap_constant(); |
| let val = const_to_value(pos.ins(), a, root); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Ireduce => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root); |
| let val = pos.ins().ireduce(ty, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Sextend => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root); |
| let val = pos.ins().sextend(ty, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Trapnz => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| |
| // NB: similar to branching instructions (see comment in the |
| // `Opcode::Brnz` match arm) trapping instructions must be the |
| // root of an optimization's right-hand side, and we get the |
| // trap code from the root of the left-hand side. Peepmatic |
| // doesn't currently represent trap codes. |
| let code = pos.func.dfg[root].trap_code().unwrap(); |
| |
| pos.ins().trapnz(a, code).into() |
| } |
| Opcode::Trapz => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| // See comment in the `Opcode::Trapnz` match arm. |
| let code = pos.func.dfg[root].trap_code().unwrap(); |
| pos.ins().trapz(a, code).into() |
| } |
| Opcode::Uextend => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root); |
| let val = pos.ins().uextend(ty, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| _ => unreachable!(), |
| } |
| } |
| |
| fn make_inst_2( |
| &self, |
| pos: &mut FuncCursor<'b>, |
| root: ValueOrInst, |
| operator: Opcode, |
| _: Type, |
| a: Part<ValueOrInst>, |
| b: Part<ValueOrInst>, |
| ) -> ValueOrInst { |
| log::trace!("make_inst_2: {:?}({:?}, {:?})", operator, a, b); |
| |
| let root = root.resolve_inst(&pos.func.dfg).unwrap(); |
| match operator { |
| Opcode::Band => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().band(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::BandImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().band_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Bor => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().bor(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::BorImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().bor_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Bxor => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().bxor(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::BxorImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().bxor_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Iadd => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().iadd(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::IaddImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().iadd_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Ifcmp => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().ifcmp(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::IfcmpImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().ifcmp_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Imul => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().imul(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::ImulImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().imul_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::IrsubImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().irsub_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Ishl => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().ishl(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::IshlImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().ishl_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Isub => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().isub(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Rotl => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().rotl(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::RotlImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().rotl_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Rotr => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().rotr(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::RotrImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().rotr_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Sdiv => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().sdiv(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::SdivImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().sdiv_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Srem => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().srem(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::SremImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().srem_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Sshr => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().sshr(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::SshrImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().sshr_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Udiv => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().udiv(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::UdivImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().udiv_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Urem => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().urem(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::UremImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().urem_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Ushr => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().ushr(a, b); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::UshrImm => { |
| let a = part_to_imm64(pos, a); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let val = pos.ins().ushr_imm(b, a); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| _ => unreachable!(), |
| } |
| } |
| |
| fn make_inst_3( |
| &self, |
| pos: &mut FuncCursor<'b>, |
| root: ValueOrInst, |
| operator: Opcode, |
| _: Type, |
| a: Part<ValueOrInst>, |
| b: Part<ValueOrInst>, |
| c: Part<ValueOrInst>, |
| ) -> ValueOrInst { |
| log::trace!("make_inst_3: {:?}({:?}, {:?}, {:?})", operator, a, b, c); |
| |
| let root = root.resolve_inst(&pos.func.dfg).unwrap(); |
| match operator { |
| Opcode::Icmp => { |
| let cond = a.unwrap_condition_code(); |
| let cond = peepmatic_to_intcc(cond); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let c = part_to_value(pos, root, c).unwrap(); |
| let val = pos.ins().icmp(cond, b, c); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::IcmpImm => { |
| let cond = a.unwrap_condition_code(); |
| let cond = peepmatic_to_intcc(cond); |
| let imm = part_to_imm64(pos, b); |
| let c = part_to_value(pos, root, c).unwrap(); |
| let val = pos.ins().icmp_imm(cond, c, imm); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| Opcode::Select => { |
| let a = part_to_value(pos, root, a).unwrap(); |
| let b = part_to_value(pos, root, b).unwrap(); |
| let c = part_to_value(pos, root, c).unwrap(); |
| let val = pos.ins().select(a, b, c); |
| pos.func.dfg.value_def(val).unwrap_inst().into() |
| } |
| _ => unreachable!(), |
| } |
| } |
| |
| fn instruction_to_constant( |
| &self, |
| pos: &mut FuncCursor<'b>, |
| value_or_inst: ValueOrInst, |
| ) -> Option<Constant> { |
| value_or_inst.to_constant(pos) |
| } |
| |
| fn instruction_result_bit_width( |
| &self, |
| pos: &mut FuncCursor<'b>, |
| value_or_inst: ValueOrInst, |
| ) -> u8 { |
| value_or_inst.result_bit_width(&pos.func.dfg) |
| } |
| |
| fn native_word_size_in_bits(&self, _pos: &mut FuncCursor<'b>) -> u8 { |
| self.pointer_bits() |
| } |
| } |
| |
| #[cfg(test)] |
| #[cfg(any(feature = "x64", feature = "x86", feature = "arm64"))] |
| mod tests { |
| use super::*; |
| use crate::isa::{lookup, TargetIsa}; |
| use crate::settings::{builder, Flags}; |
| use std::str::FromStr; |
| use target_lexicon::triple; |
| |
| fn isa() -> Box<dyn TargetIsa> { |
| // We need a triple to instantiate and run the peephole optimizer, but we |
| // don't care which one when we're just trying to trigger a rebuild of the |
| // peephole optimizer (it doesn't affect the serialized bytes at all). |
| let triple = if cfg!(any(feature = "x64", feature = "x86")) { |
| triple!("x86_64") |
| } else if cfg!(feature = "arm64") { |
| triple!("aarch64") |
| } else { |
| panic!("unknown arch") |
| }; |
| lookup(triple).unwrap().finish(Flags::new(builder())) |
| } |
| |
| #[test] |
| fn get_peepmatic_preopt() { |
| let isa = isa(); |
| let _ = preopt(&*isa); |
| } |
| } |