//! This module defines s390x-specific machine instruction types. use crate::binemit::{Addend, CodeOffset, Reloc}; use crate::ir::{types, ExternalName, Type}; use crate::isa::s390x::abi::S390xMachineDeps; use crate::isa::{CallConv, FunctionAlignment}; use crate::machinst::*; use crate::{settings, CodegenError, CodegenResult}; use alloc::boxed::Box; use alloc::vec::Vec; use regalloc2::{PReg, PRegSet}; use smallvec::SmallVec; use std::fmt::Write; use std::string::{String, ToString}; pub mod regs; pub use self::regs::*; pub mod imms; pub use self::imms::*; pub mod args; pub use self::args::*; pub mod emit; pub use self::emit::*; pub mod unwind; #[cfg(test)] mod emit_tests; //============================================================================= // Instructions (top level): definition pub use crate::isa::s390x::lower::isle::generated_code::{ ALUOp, CmpOp, FPUOp1, FPUOp2, FPUOp3, FpuRoundMode, FpuRoundOp, LaneOrder, MInst as Inst, RxSBGOp, ShiftOp, SymbolReloc, UnaryOp, VecBinaryOp, VecFloatCmpOp, VecIntCmpOp, VecShiftOp, VecUnaryOp, }; /// Additional information for (direct) ReturnCall instructions, left out of line to lower the size of /// the Inst enum. #[derive(Clone, Debug)] pub struct ReturnCallInfo { pub dest: T, pub uses: CallArgList, pub callee_pop_size: u32, } #[test] fn inst_size_test() { // This test will help with unintentionally growing the size // of the Inst enum. assert_eq!(32, std::mem::size_of::()); } /// A register pair. Enum so it can be destructured in ISLE. #[derive(Clone, Copy, Debug)] pub struct RegPair { pub hi: Reg, pub lo: Reg, } /// A writable register pair. Enum so it can be destructured in ISLE. #[derive(Clone, Copy, Debug)] pub struct WritableRegPair { pub hi: Writable, pub lo: Writable, } impl WritableRegPair { pub fn to_regpair(&self) -> RegPair { RegPair { hi: self.hi.to_reg(), lo: self.lo.to_reg(), } } } /// Supported instruction sets #[allow(non_camel_case_types)] #[derive(Debug)] pub(crate) enum InstructionSet { /// Baseline ISA for cranelift is z14. Base, /// Miscellaneous-Instruction-Extensions Facility 2 (z15) MIE2, /// Vector-Enhancements Facility 2 (z15) VXRS_EXT2, } impl Inst { /// Retrieve the ISA feature set in which the instruction is available. fn available_in_isa(&self) -> InstructionSet { match self { // These instructions are part of the baseline ISA for cranelift (z14) Inst::Nop0 | Inst::Nop2 | Inst::AluRRSImm16 { .. } | Inst::AluRR { .. } | Inst::AluRX { .. } | Inst::AluRSImm16 { .. } | Inst::AluRSImm32 { .. } | Inst::AluRUImm32 { .. } | Inst::AluRUImm16Shifted { .. } | Inst::AluRUImm32Shifted { .. } | Inst::ShiftRR { .. } | Inst::RxSBG { .. } | Inst::RxSBGTest { .. } | Inst::SMulWide { .. } | Inst::UMulWide { .. } | Inst::SDivMod32 { .. } | Inst::SDivMod64 { .. } | Inst::UDivMod32 { .. } | Inst::UDivMod64 { .. } | Inst::Flogr { .. } | Inst::CmpRR { .. } | Inst::CmpRX { .. } | Inst::CmpRSImm16 { .. } | Inst::CmpRSImm32 { .. } | Inst::CmpRUImm32 { .. } | Inst::CmpTrapRR { .. } | Inst::CmpTrapRSImm16 { .. } | Inst::CmpTrapRUImm16 { .. } | Inst::AtomicRmw { .. } | Inst::AtomicCas32 { .. } | Inst::AtomicCas64 { .. } | Inst::Fence | Inst::Load32 { .. } | Inst::Load32ZExt8 { .. } | Inst::Load32SExt8 { .. } | Inst::Load32ZExt16 { .. } | Inst::Load32SExt16 { .. } | Inst::Load64 { .. } | Inst::Load64ZExt8 { .. } | Inst::Load64SExt8 { .. } | Inst::Load64ZExt16 { .. } | Inst::Load64SExt16 { .. } | Inst::Load64ZExt32 { .. } | Inst::Load64SExt32 { .. } | Inst::LoadRev16 { .. } | Inst::LoadRev32 { .. } | Inst::LoadRev64 { .. } | Inst::Store8 { .. } | Inst::Store16 { .. } | Inst::Store32 { .. } | Inst::Store64 { .. } | Inst::StoreImm8 { .. } | Inst::StoreImm16 { .. } | Inst::StoreImm32SExt16 { .. } | Inst::StoreImm64SExt16 { .. } | Inst::StoreRev16 { .. } | Inst::StoreRev32 { .. } | Inst::StoreRev64 { .. } | Inst::LoadMultiple64 { .. } | Inst::StoreMultiple64 { .. } | Inst::Mov32 { .. } | Inst::Mov64 { .. } | Inst::MovPReg { .. } | Inst::Mov32Imm { .. } | Inst::Mov32SImm16 { .. } | Inst::Mov64SImm16 { .. } | Inst::Mov64SImm32 { .. } | Inst::Mov64UImm16Shifted { .. } | Inst::Mov64UImm32Shifted { .. } | Inst::Insert64UImm16Shifted { .. } | Inst::Insert64UImm32Shifted { .. } | Inst::LoadAR { .. } | Inst::InsertAR { .. } | Inst::Extend { .. } | Inst::CMov32 { .. } | Inst::CMov64 { .. } | Inst::CMov32SImm16 { .. } | Inst::CMov64SImm16 { .. } | Inst::FpuMove32 { .. } | Inst::FpuMove64 { .. } | Inst::FpuCMov32 { .. } | Inst::FpuCMov64 { .. } | Inst::FpuRR { .. } | Inst::FpuRRR { .. } | Inst::FpuRRRR { .. } | Inst::FpuCmp32 { .. } | Inst::FpuCmp64 { .. } | Inst::LoadFpuConst32 { .. } | Inst::LoadFpuConst64 { .. } | Inst::VecRRR { .. } | Inst::VecRR { .. } | Inst::VecShiftRR { .. } | Inst::VecSelect { .. } | Inst::VecPermute { .. } | Inst::VecPermuteDWImm { .. } | Inst::VecIntCmp { .. } | Inst::VecIntCmpS { .. } | Inst::VecFloatCmp { .. } | Inst::VecFloatCmpS { .. } | Inst::VecInt128SCmpHi { .. } | Inst::VecInt128UCmpHi { .. } | Inst::VecLoad { .. } | Inst::VecStore { .. } | Inst::VecLoadReplicate { .. } | Inst::VecMov { .. } | Inst::VecCMov { .. } | Inst::MovToVec128 { .. } | Inst::VecLoadConst { .. } | Inst::VecLoadConstReplicate { .. } | Inst::VecImmByteMask { .. } | Inst::VecImmBitMask { .. } | Inst::VecImmReplicate { .. } | Inst::VecLoadLane { .. } | Inst::VecLoadLaneUndef { .. } | Inst::VecStoreLane { .. } | Inst::VecInsertLane { .. } | Inst::VecInsertLaneUndef { .. } | Inst::VecExtractLane { .. } | Inst::VecInsertLaneImm { .. } | Inst::VecReplicateLane { .. } | Inst::AllocateArgs { .. } | Inst::Call { .. } | Inst::CallInd { .. } | Inst::ReturnCall { .. } | Inst::ReturnCallInd { .. } | Inst::Args { .. } | Inst::Rets { .. } | Inst::Ret { .. } | Inst::Jump { .. } | Inst::CondBr { .. } | Inst::TrapIf { .. } | Inst::OneWayCondBr { .. } | Inst::IndirectBr { .. } | Inst::Debugtrap | Inst::Trap { .. } | Inst::JTSequence { .. } | Inst::StackProbeLoop { .. } | Inst::LoadSymbolReloc { .. } | Inst::LoadAddr { .. } | Inst::Loop { .. } | Inst::CondBreak { .. } | Inst::Unwind { .. } | Inst::ElfTlsGetOffset { .. } => InstructionSet::Base, // These depend on the opcode Inst::AluRRR { alu_op, .. } => match alu_op { ALUOp::NotAnd32 | ALUOp::NotAnd64 => InstructionSet::MIE2, ALUOp::NotOrr32 | ALUOp::NotOrr64 => InstructionSet::MIE2, ALUOp::NotXor32 | ALUOp::NotXor64 => InstructionSet::MIE2, ALUOp::AndNot32 | ALUOp::AndNot64 => InstructionSet::MIE2, ALUOp::OrrNot32 | ALUOp::OrrNot64 => InstructionSet::MIE2, _ => InstructionSet::Base, }, Inst::UnaryRR { op, .. } => match op { UnaryOp::PopcntReg => InstructionSet::MIE2, _ => InstructionSet::Base, }, Inst::FpuRound { op, .. } => match op { FpuRoundOp::ToSInt32 | FpuRoundOp::FromSInt32 => InstructionSet::VXRS_EXT2, FpuRoundOp::ToUInt32 | FpuRoundOp::FromUInt32 => InstructionSet::VXRS_EXT2, FpuRoundOp::ToSInt32x4 | FpuRoundOp::FromSInt32x4 => InstructionSet::VXRS_EXT2, FpuRoundOp::ToUInt32x4 | FpuRoundOp::FromUInt32x4 => InstructionSet::VXRS_EXT2, _ => InstructionSet::Base, }, // These are all part of VXRS_EXT2 Inst::VecLoadRev { .. } | Inst::VecLoadByte16Rev { .. } | Inst::VecLoadByte32Rev { .. } | Inst::VecLoadByte64Rev { .. } | Inst::VecLoadElt16Rev { .. } | Inst::VecLoadElt32Rev { .. } | Inst::VecLoadElt64Rev { .. } | Inst::VecStoreRev { .. } | Inst::VecStoreByte16Rev { .. } | Inst::VecStoreByte32Rev { .. } | Inst::VecStoreByte64Rev { .. } | Inst::VecStoreElt16Rev { .. } | Inst::VecStoreElt32Rev { .. } | Inst::VecStoreElt64Rev { .. } | Inst::VecLoadReplicateRev { .. } | Inst::VecLoadLaneRev { .. } | Inst::VecLoadLaneRevUndef { .. } | Inst::VecStoreLaneRev { .. } => InstructionSet::VXRS_EXT2, Inst::DummyUse { .. } => InstructionSet::Base, } } /// Create a 128-bit move instruction. pub fn mov128(to_reg: Writable, from_reg: Reg) -> Inst { assert!(to_reg.to_reg().class() == RegClass::Float); assert!(from_reg.class() == RegClass::Float); Inst::VecMov { rd: to_reg, rn: from_reg, } } /// Create a 64-bit move instruction. pub fn mov64(to_reg: Writable, from_reg: Reg) -> Inst { assert!(to_reg.to_reg().class() == from_reg.class()); if from_reg.class() == RegClass::Int { Inst::Mov64 { rd: to_reg, rm: from_reg, } } else { Inst::FpuMove64 { rd: to_reg, rn: from_reg, } } } /// Create a 32-bit move instruction. pub fn mov32(to_reg: Writable, from_reg: Reg) -> Inst { if from_reg.class() == RegClass::Int { Inst::Mov32 { rd: to_reg, rm: from_reg, } } else { Inst::FpuMove32 { rd: to_reg, rn: from_reg, } } } /// Generic constructor for a load (zero-extending where appropriate). pub fn gen_load(into_reg: Writable, mem: MemArg, ty: Type) -> Inst { match ty { types::I8 => Inst::Load64ZExt8 { rd: into_reg, mem }, types::I16 => Inst::Load64ZExt16 { rd: into_reg, mem }, types::I32 => Inst::Load64ZExt32 { rd: into_reg, mem }, types::I64 => Inst::Load64 { rd: into_reg, mem }, types::F32 => Inst::VecLoadLaneUndef { size: 32, rd: into_reg, mem, lane_imm: 0, }, types::F64 => Inst::VecLoadLaneUndef { size: 64, rd: into_reg, mem, lane_imm: 0, }, _ if ty.is_vector() && ty.bits() == 128 => Inst::VecLoad { rd: into_reg, mem }, types::I128 => Inst::VecLoad { rd: into_reg, mem }, _ => unimplemented!("gen_load({})", ty), } } /// Generic constructor for a store. pub fn gen_store(mem: MemArg, from_reg: Reg, ty: Type) -> Inst { match ty { types::I8 => Inst::Store8 { rd: from_reg, mem }, types::I16 => Inst::Store16 { rd: from_reg, mem }, types::I32 => Inst::Store32 { rd: from_reg, mem }, types::I64 => Inst::Store64 { rd: from_reg, mem }, types::F32 => Inst::VecStoreLane { size: 32, rd: from_reg, mem, lane_imm: 0, }, types::F64 => Inst::VecStoreLane { size: 64, rd: from_reg, mem, lane_imm: 0, }, _ if ty.is_vector() && ty.bits() == 128 => Inst::VecStore { rd: from_reg, mem }, types::I128 => Inst::VecStore { rd: from_reg, mem }, _ => unimplemented!("gen_store({})", ty), } } } //============================================================================= // Instructions: get_regs fn memarg_operands(memarg: &mut MemArg, collector: &mut impl OperandVisitor) { match memarg { MemArg::BXD12 { base, index, .. } | MemArg::BXD20 { base, index, .. } => { collector.reg_use(base); collector.reg_use(index); } MemArg::Label { .. } | MemArg::Symbol { .. } => {} MemArg::RegOffset { reg, .. } => { collector.reg_use(reg); } MemArg::InitialSPOffset { .. } | MemArg::NominalSPOffset { .. } | MemArg::SlotOffset { .. } => {} } // mem_finalize might require %r1 to hold (part of) the address. // Conservatively assume this will always be necessary here. collector.reg_fixed_nonallocatable(gpr_preg(1)); } fn s390x_get_operands(inst: &mut Inst, collector: &mut DenyReuseVisitor) { match inst { Inst::AluRRR { rd, rn, rm, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(rm); } Inst::AluRRSImm16 { rd, rn, .. } => { collector.reg_def(rd); collector.reg_use(rn); } Inst::AluRR { rd, ri, rm, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); collector.reg_use(rm); } Inst::AluRX { rd, ri, mem, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); memarg_operands(mem, collector); } Inst::AluRSImm16 { rd, ri, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); } Inst::AluRSImm32 { rd, ri, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); } Inst::AluRUImm32 { rd, ri, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); } Inst::AluRUImm16Shifted { rd, ri, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); } Inst::AluRUImm32Shifted { rd, ri, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); } Inst::SMulWide { rd, rn, rm } => { collector.reg_use(rn); collector.reg_use(rm); // FIXME: The pair is hard-coded as %r2/%r3 because regalloc cannot handle pairs. If // that changes, all the hard-coded uses of %r2/%r3 can be changed. collector.reg_fixed_def(&mut rd.hi, gpr(2)); collector.reg_fixed_def(&mut rd.lo, gpr(3)); } Inst::UMulWide { rd, ri, rn } => { collector.reg_use(rn); collector.reg_fixed_def(&mut rd.hi, gpr(2)); collector.reg_fixed_def(&mut rd.lo, gpr(3)); collector.reg_fixed_use(ri, gpr(3)); } Inst::SDivMod32 { rd, ri, rn } | Inst::SDivMod64 { rd, ri, rn } => { collector.reg_use(rn); collector.reg_fixed_def(&mut rd.hi, gpr(2)); collector.reg_fixed_def(&mut rd.lo, gpr(3)); collector.reg_fixed_use(ri, gpr(3)); } Inst::UDivMod32 { rd, ri, rn } | Inst::UDivMod64 { rd, ri, rn } => { collector.reg_use(rn); collector.reg_fixed_def(&mut rd.hi, gpr(2)); collector.reg_fixed_def(&mut rd.lo, gpr(3)); collector.reg_fixed_use(&mut ri.hi, gpr(2)); collector.reg_fixed_use(&mut ri.lo, gpr(3)); } Inst::Flogr { rd, rn } => { collector.reg_use(rn); collector.reg_fixed_def(&mut rd.hi, gpr(2)); collector.reg_fixed_def(&mut rd.lo, gpr(3)); } Inst::ShiftRR { rd, rn, shift_reg, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(shift_reg); } Inst::RxSBG { rd, ri, rn, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); collector.reg_use(rn); } Inst::RxSBGTest { rd, rn, .. } => { collector.reg_use(rd); collector.reg_use(rn); } Inst::UnaryRR { rd, rn, .. } => { collector.reg_def(rd); collector.reg_use(rn); } Inst::CmpRR { rn, rm, .. } => { collector.reg_use(rn); collector.reg_use(rm); } Inst::CmpRX { rn, mem, .. } => { collector.reg_use(rn); memarg_operands(mem, collector); } Inst::CmpRSImm16 { rn, .. } => { collector.reg_use(rn); } Inst::CmpRSImm32 { rn, .. } => { collector.reg_use(rn); } Inst::CmpRUImm32 { rn, .. } => { collector.reg_use(rn); } Inst::CmpTrapRR { rn, rm, .. } => { collector.reg_use(rn); collector.reg_use(rm); } Inst::CmpTrapRSImm16 { rn, .. } => { collector.reg_use(rn); } Inst::CmpTrapRUImm16 { rn, .. } => { collector.reg_use(rn); } Inst::AtomicRmw { rd, rn, mem, .. } => { collector.reg_def(rd); collector.reg_use(rn); memarg_operands(mem, collector); } Inst::AtomicCas32 { rd, ri, rn, mem, .. } | Inst::AtomicCas64 { rd, ri, rn, mem, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); collector.reg_use(rn); memarg_operands(mem, collector); } Inst::Fence => {} Inst::Load32 { rd, mem, .. } | Inst::Load32ZExt8 { rd, mem, .. } | Inst::Load32SExt8 { rd, mem, .. } | Inst::Load32ZExt16 { rd, mem, .. } | Inst::Load32SExt16 { rd, mem, .. } | Inst::Load64 { rd, mem, .. } | Inst::Load64ZExt8 { rd, mem, .. } | Inst::Load64SExt8 { rd, mem, .. } | Inst::Load64ZExt16 { rd, mem, .. } | Inst::Load64SExt16 { rd, mem, .. } | Inst::Load64ZExt32 { rd, mem, .. } | Inst::Load64SExt32 { rd, mem, .. } | Inst::LoadRev16 { rd, mem, .. } | Inst::LoadRev32 { rd, mem, .. } | Inst::LoadRev64 { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::Store8 { rd, mem, .. } | Inst::Store16 { rd, mem, .. } | Inst::Store32 { rd, mem, .. } | Inst::Store64 { rd, mem, .. } | Inst::StoreRev16 { rd, mem, .. } | Inst::StoreRev32 { rd, mem, .. } | Inst::StoreRev64 { rd, mem, .. } => { collector.reg_use(rd); memarg_operands(mem, collector); } Inst::StoreImm8 { mem, .. } | Inst::StoreImm16 { mem, .. } | Inst::StoreImm32SExt16 { mem, .. } | Inst::StoreImm64SExt16 { mem, .. } => { memarg_operands(mem, collector); } Inst::LoadMultiple64 { rt, rt2, mem, .. } => { memarg_operands(mem, collector); let first_regnum = rt.to_reg().to_real_reg().unwrap().hw_enc(); let last_regnum = rt2.to_reg().to_real_reg().unwrap().hw_enc(); for regnum in first_regnum..last_regnum + 1 { collector.reg_fixed_nonallocatable(gpr_preg(regnum)); } } Inst::StoreMultiple64 { rt, rt2, mem, .. } => { memarg_operands(mem, collector); let first_regnum = rt.to_real_reg().unwrap().hw_enc(); let last_regnum = rt2.to_real_reg().unwrap().hw_enc(); for regnum in first_regnum..last_regnum + 1 { collector.reg_fixed_nonallocatable(gpr_preg(regnum)); } } Inst::Mov64 { rd, rm } => { collector.reg_def(rd); collector.reg_use(rm); } Inst::MovPReg { rd, rm } => { collector.reg_def(rd); collector.reg_fixed_nonallocatable(*rm); } Inst::Mov32 { rd, rm } => { collector.reg_def(rd); collector.reg_use(rm); } Inst::Mov32Imm { rd, .. } | Inst::Mov32SImm16 { rd, .. } | Inst::Mov64SImm16 { rd, .. } | Inst::Mov64SImm32 { rd, .. } | Inst::Mov64UImm16Shifted { rd, .. } | Inst::Mov64UImm32Shifted { rd, .. } => { collector.reg_def(rd); } Inst::CMov32 { rd, ri, rm, .. } | Inst::CMov64 { rd, ri, rm, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); collector.reg_use(rm); } Inst::CMov32SImm16 { rd, ri, .. } | Inst::CMov64SImm16 { rd, ri, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); } Inst::Insert64UImm16Shifted { rd, ri, .. } | Inst::Insert64UImm32Shifted { rd, ri, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); } Inst::LoadAR { rd, .. } => { collector.reg_def(rd); } Inst::InsertAR { rd, ri, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); } Inst::FpuMove32 { rd, rn } | Inst::FpuMove64 { rd, rn } => { collector.reg_def(rd); collector.reg_use(rn); } Inst::FpuCMov32 { rd, ri, rm, .. } | Inst::FpuCMov64 { rd, ri, rm, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); collector.reg_use(rm); } Inst::FpuRR { rd, rn, .. } => { collector.reg_def(rd); collector.reg_use(rn); } Inst::FpuRRR { rd, rn, rm, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(rm); } Inst::FpuRRRR { rd, rn, rm, ra, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(rm); collector.reg_use(ra); } Inst::FpuCmp32 { rn, rm } | Inst::FpuCmp64 { rn, rm } => { collector.reg_use(rn); collector.reg_use(rm); } Inst::LoadFpuConst32 { rd, .. } | Inst::LoadFpuConst64 { rd, .. } => { collector.reg_def(rd); collector.reg_fixed_nonallocatable(gpr_preg(1)); } Inst::FpuRound { rd, rn, .. } => { collector.reg_def(rd); collector.reg_use(rn); } Inst::VecRRR { rd, rn, rm, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(rm); } Inst::VecRR { rd, rn, .. } => { collector.reg_def(rd); collector.reg_use(rn); } Inst::VecShiftRR { rd, rn, shift_reg, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(shift_reg); } Inst::VecSelect { rd, rn, rm, ra, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(rm); collector.reg_use(ra); } Inst::VecPermute { rd, rn, rm, ra, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(rm); collector.reg_use(ra); } Inst::VecPermuteDWImm { rd, rn, rm, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(rm); } Inst::VecIntCmp { rd, rn, rm, .. } | Inst::VecIntCmpS { rd, rn, rm, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(rm); } Inst::VecFloatCmp { rd, rn, rm, .. } | Inst::VecFloatCmpS { rd, rn, rm, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(rm); } Inst::VecInt128SCmpHi { tmp, rn, rm, .. } | Inst::VecInt128UCmpHi { tmp, rn, rm, .. } => { collector.reg_def(tmp); collector.reg_use(rn); collector.reg_use(rm); } Inst::VecLoad { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecLoadRev { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecLoadByte16Rev { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecLoadByte32Rev { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecLoadByte64Rev { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecLoadElt16Rev { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecLoadElt32Rev { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecLoadElt64Rev { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecStore { rd, mem, .. } => { collector.reg_use(rd); memarg_operands(mem, collector); } Inst::VecStoreRev { rd, mem, .. } => { collector.reg_use(rd); memarg_operands(mem, collector); } Inst::VecStoreByte16Rev { rd, mem, .. } => { collector.reg_use(rd); memarg_operands(mem, collector); } Inst::VecStoreByte32Rev { rd, mem, .. } => { collector.reg_use(rd); memarg_operands(mem, collector); } Inst::VecStoreByte64Rev { rd, mem, .. } => { collector.reg_use(rd); memarg_operands(mem, collector); } Inst::VecStoreElt16Rev { rd, mem, .. } => { collector.reg_use(rd); memarg_operands(mem, collector); } Inst::VecStoreElt32Rev { rd, mem, .. } => { collector.reg_use(rd); memarg_operands(mem, collector); } Inst::VecStoreElt64Rev { rd, mem, .. } => { collector.reg_use(rd); memarg_operands(mem, collector); } Inst::VecLoadReplicate { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecLoadReplicateRev { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecMov { rd, rn } => { collector.reg_def(rd); collector.reg_use(rn); } Inst::VecCMov { rd, ri, rm, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); collector.reg_use(rm); } Inst::MovToVec128 { rd, rn, rm } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(rm); } Inst::VecLoadConst { rd, .. } | Inst::VecLoadConstReplicate { rd, .. } => { collector.reg_def(rd); collector.reg_fixed_nonallocatable(gpr_preg(1)); } Inst::VecImmByteMask { rd, .. } => { collector.reg_def(rd); } Inst::VecImmBitMask { rd, .. } => { collector.reg_def(rd); } Inst::VecImmReplicate { rd, .. } => { collector.reg_def(rd); } Inst::VecLoadLane { rd, ri, mem, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); memarg_operands(mem, collector); } Inst::VecLoadLaneUndef { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecStoreLaneRev { rd, mem, .. } => { collector.reg_use(rd); memarg_operands(mem, collector); } Inst::VecLoadLaneRevUndef { rd, mem, .. } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::VecStoreLane { rd, mem, .. } => { collector.reg_use(rd); memarg_operands(mem, collector); } Inst::VecLoadLaneRev { rd, ri, mem, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); memarg_operands(mem, collector); } Inst::VecInsertLane { rd, ri, rn, lane_reg, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); collector.reg_use(rn); collector.reg_use(lane_reg); } Inst::VecInsertLaneUndef { rd, rn, lane_reg, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(lane_reg); } Inst::VecExtractLane { rd, rn, lane_reg, .. } => { collector.reg_def(rd); collector.reg_use(rn); collector.reg_use(lane_reg); } Inst::VecInsertLaneImm { rd, ri, .. } => { collector.reg_reuse_def(rd, 1); collector.reg_use(ri); } Inst::VecReplicateLane { rd, rn, .. } => { collector.reg_def(rd); collector.reg_use(rn); } Inst::Extend { rd, rn, .. } => { collector.reg_def(rd); collector.reg_use(rn); } Inst::AllocateArgs { .. } => {} Inst::Call { link, info, .. } => { let CallInfo { uses, defs, clobbers, .. } = &mut **info; for CallArgPair { vreg, preg } in uses { collector.reg_fixed_use(vreg, *preg); } let mut clobbers = *clobbers; clobbers.add(link.to_reg().to_real_reg().unwrap().into()); for CallRetPair { vreg, preg } in defs { clobbers.remove(PReg::from(preg.to_real_reg().unwrap())); collector.reg_fixed_def(vreg, *preg); } collector.reg_clobbers(clobbers); } Inst::CallInd { link, info } => { let CallInfo { dest, uses, defs, clobbers, .. } = &mut **info; collector.reg_use(dest); for CallArgPair { vreg, preg } in uses { collector.reg_fixed_use(vreg, *preg); } let mut clobbers = *clobbers; clobbers.add(link.to_reg().to_real_reg().unwrap().into()); for CallRetPair { vreg, preg } in defs { clobbers.remove(PReg::from(preg.to_real_reg().unwrap())); collector.reg_fixed_def(vreg, *preg); } collector.reg_clobbers(clobbers); } Inst::ReturnCall { info } => { let ReturnCallInfo { uses, .. } = &mut **info; for CallArgPair { vreg, preg } in uses { collector.reg_fixed_use(vreg, *preg); } } Inst::ReturnCallInd { info } => { let ReturnCallInfo { dest, uses, .. } = &mut **info; collector.reg_use(dest); for CallArgPair { vreg, preg } in uses { collector.reg_fixed_use(vreg, *preg); } } Inst::ElfTlsGetOffset { tls_offset, got, got_offset, .. } => { collector.reg_fixed_use(got, gpr(12)); collector.reg_fixed_use(got_offset, gpr(2)); collector.reg_fixed_def(tls_offset, gpr(2)); let mut clobbers = S390xMachineDeps::get_regs_clobbered_by_call(CallConv::SystemV); clobbers.add(gpr_preg(14)); clobbers.remove(gpr_preg(2)); collector.reg_clobbers(clobbers); } Inst::Args { args } => { for ArgPair { vreg, preg } in args { collector.reg_fixed_def(vreg, *preg); } } Inst::Rets { rets } => { for RetPair { vreg, preg } in rets { collector.reg_fixed_use(vreg, *preg); } } Inst::Ret { .. } => { // NOTE: we explicitly don't mark the link register as used here, as the use is only in // the epilog where callee-save registers are restored. } Inst::Jump { .. } => {} Inst::IndirectBr { rn, .. } => { collector.reg_use(rn); } Inst::CondBr { .. } | Inst::OneWayCondBr { .. } => {} Inst::Nop0 | Inst::Nop2 => {} Inst::Debugtrap => {} Inst::Trap { .. } => {} Inst::TrapIf { .. } => {} Inst::JTSequence { ridx, .. } => { collector.reg_use(ridx); collector.reg_fixed_nonallocatable(gpr_preg(1)); } Inst::LoadSymbolReloc { rd, .. } => { collector.reg_def(rd); collector.reg_fixed_nonallocatable(gpr_preg(1)); } Inst::LoadAddr { rd, mem } => { collector.reg_def(rd); memarg_operands(mem, collector); } Inst::StackProbeLoop { probe_count, .. } => { collector.reg_early_def(probe_count); } Inst::Loop { body, .. } => { // `reuse_def` constraints can't be permitted in a Loop instruction because the operand // index will always be relative to the Loop instruction, not the individual // instruction in the loop body. However, fixed-nonallocatable registers used with // instructions that would have emitted `reuse_def` constraints are fine. let mut collector = DenyReuseVisitor { inner: collector.inner, deny_reuse: true, }; for inst in body { s390x_get_operands(inst, &mut collector); } } Inst::CondBreak { .. } => {} Inst::Unwind { .. } => {} Inst::DummyUse { reg } => { collector.reg_use(reg); } } } struct DenyReuseVisitor<'a, T> { inner: &'a mut T, deny_reuse: bool, } impl OperandVisitor for DenyReuseVisitor<'_, T> { fn add_operand( &mut self, reg: &mut Reg, constraint: regalloc2::OperandConstraint, kind: regalloc2::OperandKind, pos: regalloc2::OperandPos, ) { debug_assert!( !self.deny_reuse || !matches!(constraint, regalloc2::OperandConstraint::Reuse(_)) ); self.inner.add_operand(reg, constraint, kind, pos); } fn debug_assert_is_allocatable_preg(&self, reg: regalloc2::PReg, expected: bool) { self.inner.debug_assert_is_allocatable_preg(reg, expected); } fn reg_clobbers(&mut self, regs: PRegSet) { self.inner.reg_clobbers(regs); } } //============================================================================= // Instructions: misc functions and external interface impl MachInst for Inst { type ABIMachineSpec = S390xMachineDeps; type LabelUse = LabelUse; const TRAP_OPCODE: &'static [u8] = &[0, 0]; fn get_operands(&mut self, collector: &mut impl OperandVisitor) { s390x_get_operands( self, &mut DenyReuseVisitor { inner: collector, deny_reuse: false, }, ); } fn is_move(&self) -> Option<(Writable, Reg)> { match self { &Inst::Mov32 { rd, rm } => Some((rd, rm)), &Inst::Mov64 { rd, rm } => Some((rd, rm)), &Inst::FpuMove32 { rd, rn } => Some((rd, rn)), &Inst::FpuMove64 { rd, rn } => Some((rd, rn)), &Inst::VecMov { rd, rn } => Some((rd, rn)), _ => None, } } fn is_included_in_clobbers(&self) -> bool { // We exclude call instructions from the clobber-set when they are calls // from caller to callee with the same ABI. Such calls cannot possibly // force any new registers to be saved in the prologue, because anything // that the callee clobbers, the caller is also allowed to clobber. This // both saves work and enables us to more precisely follow the // half-caller-save, half-callee-save SysV ABI for some vector // registers. match self { &Inst::Args { .. } => false, &Inst::Call { ref info, .. } => info.caller_conv != info.callee_conv, &Inst::CallInd { ref info, .. } => info.caller_conv != info.callee_conv, &Inst::ElfTlsGetOffset { .. } => false, _ => true, } } fn is_trap(&self) -> bool { match self { Self::Trap { .. } => true, _ => false, } } fn is_args(&self) -> bool { match self { Self::Args { .. } => true, _ => false, } } fn is_term(&self) -> MachTerminator { match self { &Inst::Rets { .. } => MachTerminator::Ret, &Inst::ReturnCall { .. } | &Inst::ReturnCallInd { .. } => MachTerminator::RetCall, &Inst::Jump { .. } => MachTerminator::Uncond, &Inst::CondBr { .. } => MachTerminator::Cond, &Inst::OneWayCondBr { .. } => { // Explicitly invisible to CFG processing. MachTerminator::None } &Inst::IndirectBr { .. } => MachTerminator::Indirect, &Inst::JTSequence { .. } => MachTerminator::Indirect, _ => MachTerminator::None, } } fn is_mem_access(&self) -> bool { panic!("TODO FILL ME OUT") } fn is_safepoint(&self) -> bool { match self { Inst::Call { .. } | Inst::CallInd { .. } => true, _ => false, } } fn gen_move(to_reg: Writable, from_reg: Reg, ty: Type) -> Inst { assert!(ty.bits() <= 128); if ty.bits() <= 32 { Inst::mov32(to_reg, from_reg) } else if ty.bits() <= 64 { Inst::mov64(to_reg, from_reg) } else { Inst::mov128(to_reg, from_reg) } } fn gen_nop(preferred_size: usize) -> Inst { if preferred_size == 0 { Inst::Nop0 } else { // We can't give a NOP (or any insn) < 2 bytes. assert!(preferred_size >= 2); Inst::Nop2 } } fn rc_for_type(ty: Type) -> CodegenResult<(&'static [RegClass], &'static [Type])> { match ty { types::I8 => Ok((&[RegClass::Int], &[types::I8])), types::I16 => Ok((&[RegClass::Int], &[types::I16])), types::I32 => Ok((&[RegClass::Int], &[types::I32])), types::I64 => Ok((&[RegClass::Int], &[types::I64])), types::F32 => Ok((&[RegClass::Float], &[types::F32])), types::F64 => Ok((&[RegClass::Float], &[types::F64])), types::I128 => Ok((&[RegClass::Float], &[types::I128])), _ if ty.is_vector() && ty.bits() == 128 => Ok((&[RegClass::Float], &[types::I8X16])), _ => Err(CodegenError::Unsupported(format!( "Unexpected SSA-value type: {ty}" ))), } } fn canonical_type_for_rc(rc: RegClass) -> Type { match rc { RegClass::Int => types::I64, RegClass::Float => types::I8X16, RegClass::Vector => unreachable!(), } } fn gen_jump(target: MachLabel) -> Inst { Inst::Jump { dest: target } } fn worst_case_size() -> CodeOffset { // The maximum size, in bytes, of any `Inst`'s emitted code. We have at least one case of // an 8-instruction sequence (saturating int-to-float conversions) with three embedded // 64-bit f64 constants. // // Note that inline jump-tables handle island/pool insertion separately, so we do not need // to account for them here (otherwise the worst case would be 2^31 * 4, clearly not // feasible for other reasons). 44 } fn ref_type_regclass(_: &settings::Flags) -> RegClass { RegClass::Int } fn gen_dummy_use(reg: Reg) -> Inst { Inst::DummyUse { reg } } fn function_alignment() -> FunctionAlignment { FunctionAlignment { minimum: 4, preferred: 4, } } } //============================================================================= // Pretty-printing of instructions. fn mem_finalize_for_show(mem: &MemArg, state: &EmitState, mi: MemInstType) -> (String, MemArg) { let (mem_insts, mem) = mem_finalize(mem, state, mi); let mut mem_str = mem_insts .into_iter() .map(|inst| inst.print_with_state(&mut EmitState::default())) .collect::>() .join(" ; "); if !mem_str.is_empty() { mem_str += " ; "; } (mem_str, mem) } impl Inst { fn print_with_state(&self, state: &mut EmitState) -> String { match self { &Inst::Nop0 => "nop-zero-len".to_string(), &Inst::Nop2 => "nop".to_string(), &Inst::AluRRR { alu_op, rd, rn, rm } => { let (op, have_rr) = match alu_op { ALUOp::Add32 => ("ark", true), ALUOp::Add64 => ("agrk", true), ALUOp::AddLogical32 => ("alrk", true), ALUOp::AddLogical64 => ("algrk", true), ALUOp::Sub32 => ("srk", true), ALUOp::Sub64 => ("sgrk", true), ALUOp::SubLogical32 => ("slrk", true), ALUOp::SubLogical64 => ("slgrk", true), ALUOp::Mul32 => ("msrkc", true), ALUOp::Mul64 => ("msgrkc", true), ALUOp::And32 => ("nrk", true), ALUOp::And64 => ("ngrk", true), ALUOp::Orr32 => ("ork", true), ALUOp::Orr64 => ("ogrk", true), ALUOp::Xor32 => ("xrk", true), ALUOp::Xor64 => ("xgrk", true), ALUOp::NotAnd32 => ("nnrk", false), ALUOp::NotAnd64 => ("nngrk", false), ALUOp::NotOrr32 => ("nork", false), ALUOp::NotOrr64 => ("nogrk", false), ALUOp::NotXor32 => ("nxrk", false), ALUOp::NotXor64 => ("nxgrk", false), ALUOp::AndNot32 => ("ncrk", false), ALUOp::AndNot64 => ("ncgrk", false), ALUOp::OrrNot32 => ("ocrk", false), ALUOp::OrrNot64 => ("ocgrk", false), _ => unreachable!(), }; if have_rr && rd.to_reg() == rn { let inst = Inst::AluRR { alu_op, rd, ri: rd.to_reg(), rm, }; return inst.print_with_state(state); } let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); format!("{op} {rd}, {rn}, {rm}") } &Inst::AluRRSImm16 { alu_op, rd, rn, imm, } => { if rd.to_reg() == rn { let inst = Inst::AluRSImm16 { alu_op, rd, ri: rd.to_reg(), imm, }; return inst.print_with_state(state); } let op = match alu_op { ALUOp::Add32 => "ahik", ALUOp::Add64 => "aghik", _ => unreachable!(), }; let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); format!("{op} {rd}, {rn}, {imm}") } &Inst::AluRR { alu_op, rd, ri, rm } => { let op = match alu_op { ALUOp::Add32 => "ar", ALUOp::Add64 => "agr", ALUOp::Add64Ext32 => "agfr", ALUOp::AddLogical32 => "alr", ALUOp::AddLogical64 => "algr", ALUOp::AddLogical64Ext32 => "algfr", ALUOp::Sub32 => "sr", ALUOp::Sub64 => "sgr", ALUOp::Sub64Ext32 => "sgfr", ALUOp::SubLogical32 => "slr", ALUOp::SubLogical64 => "slgr", ALUOp::SubLogical64Ext32 => "slgfr", ALUOp::Mul32 => "msr", ALUOp::Mul64 => "msgr", ALUOp::Mul64Ext32 => "msgfr", ALUOp::And32 => "nr", ALUOp::And64 => "ngr", ALUOp::Orr32 => "or", ALUOp::Orr64 => "ogr", ALUOp::Xor32 => "xr", ALUOp::Xor64 => "xgr", _ => unreachable!(), }; let rd = pretty_print_reg_mod(rd, ri); let rm = pretty_print_reg(rm); format!("{op} {rd}, {rm}") } &Inst::AluRX { alu_op, rd, ri, ref mem, } => { let (opcode_rx, opcode_rxy) = match alu_op { ALUOp::Add32 => (Some("a"), Some("ay")), ALUOp::Add32Ext16 => (Some("ah"), Some("ahy")), ALUOp::Add64 => (None, Some("ag")), ALUOp::Add64Ext16 => (None, Some("agh")), ALUOp::Add64Ext32 => (None, Some("agf")), ALUOp::AddLogical32 => (Some("al"), Some("aly")), ALUOp::AddLogical64 => (None, Some("alg")), ALUOp::AddLogical64Ext32 => (None, Some("algf")), ALUOp::Sub32 => (Some("s"), Some("sy")), ALUOp::Sub32Ext16 => (Some("sh"), Some("shy")), ALUOp::Sub64 => (None, Some("sg")), ALUOp::Sub64Ext16 => (None, Some("sgh")), ALUOp::Sub64Ext32 => (None, Some("sgf")), ALUOp::SubLogical32 => (Some("sl"), Some("sly")), ALUOp::SubLogical64 => (None, Some("slg")), ALUOp::SubLogical64Ext32 => (None, Some("slgf")), ALUOp::Mul32 => (Some("ms"), Some("msy")), ALUOp::Mul32Ext16 => (Some("mh"), Some("mhy")), ALUOp::Mul64 => (None, Some("msg")), ALUOp::Mul64Ext16 => (None, Some("mgh")), ALUOp::Mul64Ext32 => (None, Some("msgf")), ALUOp::And32 => (Some("n"), Some("ny")), ALUOp::And64 => (None, Some("ng")), ALUOp::Orr32 => (Some("o"), Some("oy")), ALUOp::Orr64 => (None, Some("og")), ALUOp::Xor32 => (Some("x"), Some("xy")), ALUOp::Xor64 => (None, Some("xg")), _ => unreachable!(), }; let rd = pretty_print_reg_mod(rd, ri); let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: opcode_rx.is_some(), have_d20: opcode_rxy.is_some(), have_pcrel: false, have_unaligned_pcrel: false, have_index: true, }, ); let op = match &mem { &MemArg::BXD12 { .. } => opcode_rx, &MemArg::BXD20 { .. } => opcode_rxy, _ => unreachable!(), }; let mem = mem.pretty_print_default(); format!("{}{} {}, {}", mem_str, op.unwrap(), rd, mem) } &Inst::AluRSImm16 { alu_op, rd, ri, imm, } => { let op = match alu_op { ALUOp::Add32 => "ahi", ALUOp::Add64 => "aghi", ALUOp::Mul32 => "mhi", ALUOp::Mul64 => "mghi", _ => unreachable!(), }; let rd = pretty_print_reg_mod(rd, ri); format!("{op} {rd}, {imm}") } &Inst::AluRSImm32 { alu_op, rd, ri, imm, } => { let op = match alu_op { ALUOp::Add32 => "afi", ALUOp::Add64 => "agfi", ALUOp::Mul32 => "msfi", ALUOp::Mul64 => "msgfi", _ => unreachable!(), }; let rd = pretty_print_reg_mod(rd, ri); format!("{op} {rd}, {imm}") } &Inst::AluRUImm32 { alu_op, rd, ri, imm, } => { let op = match alu_op { ALUOp::AddLogical32 => "alfi", ALUOp::AddLogical64 => "algfi", ALUOp::SubLogical32 => "slfi", ALUOp::SubLogical64 => "slgfi", _ => unreachable!(), }; let rd = pretty_print_reg_mod(rd, ri); format!("{op} {rd}, {imm}") } &Inst::AluRUImm16Shifted { alu_op, rd, ri, imm, } => { let op = match (alu_op, imm.shift) { (ALUOp::And32, 0) => "nill", (ALUOp::And32, 1) => "nilh", (ALUOp::And64, 0) => "nill", (ALUOp::And64, 1) => "nilh", (ALUOp::And64, 2) => "nihl", (ALUOp::And64, 3) => "nihh", (ALUOp::Orr32, 0) => "oill", (ALUOp::Orr32, 1) => "oilh", (ALUOp::Orr64, 0) => "oill", (ALUOp::Orr64, 1) => "oilh", (ALUOp::Orr64, 2) => "oihl", (ALUOp::Orr64, 3) => "oihh", _ => unreachable!(), }; let rd = pretty_print_reg_mod(rd, ri); format!("{} {}, {}", op, rd, imm.bits) } &Inst::AluRUImm32Shifted { alu_op, rd, ri, imm, } => { let op = match (alu_op, imm.shift) { (ALUOp::And32, 0) => "nilf", (ALUOp::And64, 0) => "nilf", (ALUOp::And64, 1) => "nihf", (ALUOp::Orr32, 0) => "oilf", (ALUOp::Orr64, 0) => "oilf", (ALUOp::Orr64, 1) => "oihf", (ALUOp::Xor32, 0) => "xilf", (ALUOp::Xor64, 0) => "xilf", (ALUOp::Xor64, 1) => "xihf", _ => unreachable!(), }; let rd = pretty_print_reg_mod(rd, ri); format!("{} {}, {}", op, rd, imm.bits) } &Inst::SMulWide { rd, rn, rm } => { let op = "mgrk"; let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); let rd = pretty_print_regpair(rd.to_regpair()); format!("{op} {rd}, {rn}, {rm}") } &Inst::UMulWide { rd, ri, rn } => { let op = "mlgr"; let rn = pretty_print_reg(rn); let rd = pretty_print_regpair_mod_lo(rd, ri); format!("{op} {rd}, {rn}") } &Inst::SDivMod32 { rd, ri, rn } => { let op = "dsgfr"; let rn = pretty_print_reg(rn); let rd = pretty_print_regpair_mod_lo(rd, ri); format!("{op} {rd}, {rn}") } &Inst::SDivMod64 { rd, ri, rn } => { let op = "dsgr"; let rn = pretty_print_reg(rn); let rd = pretty_print_regpair_mod_lo(rd, ri); format!("{op} {rd}, {rn}") } &Inst::UDivMod32 { rd, ri, rn } => { let op = "dlr"; let rn = pretty_print_reg(rn); let rd = pretty_print_regpair_mod(rd, ri); format!("{op} {rd}, {rn}") } &Inst::UDivMod64 { rd, ri, rn } => { let op = "dlgr"; let rn = pretty_print_reg(rn); let rd = pretty_print_regpair_mod(rd, ri); format!("{op} {rd}, {rn}") } &Inst::Flogr { rd, rn } => { let op = "flogr"; let rn = pretty_print_reg(rn); let rd = pretty_print_regpair(rd.to_regpair()); format!("{op} {rd}, {rn}") } &Inst::ShiftRR { shift_op, rd, rn, shift_imm, shift_reg, } => { let op = match shift_op { ShiftOp::RotL32 => "rll", ShiftOp::RotL64 => "rllg", ShiftOp::LShL32 => "sllk", ShiftOp::LShL64 => "sllg", ShiftOp::LShR32 => "srlk", ShiftOp::LShR64 => "srlg", ShiftOp::AShR32 => "srak", ShiftOp::AShR64 => "srag", }; let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let shift_reg = if shift_reg != zero_reg() { format!("({})", pretty_print_reg(shift_reg)) } else { "".to_string() }; format!("{op} {rd}, {rn}, {shift_imm}{shift_reg}") } &Inst::RxSBG { op, rd, ri, rn, start_bit, end_bit, rotate_amt, } => { let op = match op { RxSBGOp::Insert => "risbgn", RxSBGOp::And => "rnsbg", RxSBGOp::Or => "rosbg", RxSBGOp::Xor => "rxsbg", }; let rd = pretty_print_reg_mod(rd, ri); let rn = pretty_print_reg(rn); format!( "{} {}, {}, {}, {}, {}", op, rd, rn, start_bit, end_bit, (rotate_amt as u8) & 63 ) } &Inst::RxSBGTest { op, rd, rn, start_bit, end_bit, rotate_amt, } => { let op = match op { RxSBGOp::And => "rnsbg", RxSBGOp::Or => "rosbg", RxSBGOp::Xor => "rxsbg", _ => unreachable!(), }; let rd = pretty_print_reg(rd); let rn = pretty_print_reg(rn); format!( "{} {}, {}, {}, {}, {}", op, rd, rn, start_bit | 0x80, end_bit, (rotate_amt as u8) & 63 ) } &Inst::UnaryRR { op, rd, rn } => { let (op, extra) = match op { UnaryOp::Abs32 => ("lpr", ""), UnaryOp::Abs64 => ("lpgr", ""), UnaryOp::Abs64Ext32 => ("lpgfr", ""), UnaryOp::Neg32 => ("lcr", ""), UnaryOp::Neg64 => ("lcgr", ""), UnaryOp::Neg64Ext32 => ("lcgfr", ""), UnaryOp::PopcntByte => ("popcnt", ""), UnaryOp::PopcntReg => ("popcnt", ", 8"), UnaryOp::BSwap32 => ("lrvr", ""), UnaryOp::BSwap64 => ("lrvgr", ""), }; let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); format!("{op} {rd}, {rn}{extra}") } &Inst::CmpRR { op, rn, rm } => { let op = match op { CmpOp::CmpS32 => "cr", CmpOp::CmpS64 => "cgr", CmpOp::CmpS64Ext32 => "cgfr", CmpOp::CmpL32 => "clr", CmpOp::CmpL64 => "clgr", CmpOp::CmpL64Ext32 => "clgfr", _ => unreachable!(), }; let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); format!("{op} {rn}, {rm}") } &Inst::CmpRX { op, rn, ref mem } => { let (opcode_rx, opcode_rxy, opcode_ril) = match op { CmpOp::CmpS32 => (Some("c"), Some("cy"), Some("crl")), CmpOp::CmpS32Ext16 => (Some("ch"), Some("chy"), Some("chrl")), CmpOp::CmpS64 => (None, Some("cg"), Some("cgrl")), CmpOp::CmpS64Ext16 => (None, Some("cgh"), Some("cghrl")), CmpOp::CmpS64Ext32 => (None, Some("cgf"), Some("cgfrl")), CmpOp::CmpL32 => (Some("cl"), Some("cly"), Some("clrl")), CmpOp::CmpL32Ext16 => (None, None, Some("clhrl")), CmpOp::CmpL64 => (None, Some("clg"), Some("clgrl")), CmpOp::CmpL64Ext16 => (None, None, Some("clghrl")), CmpOp::CmpL64Ext32 => (None, Some("clgf"), Some("clgfrl")), }; let rn = pretty_print_reg(rn); let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: opcode_rx.is_some(), have_d20: opcode_rxy.is_some(), have_pcrel: opcode_ril.is_some(), have_unaligned_pcrel: false, have_index: true, }, ); let op = match &mem { &MemArg::BXD12 { .. } => opcode_rx, &MemArg::BXD20 { .. } => opcode_rxy, &MemArg::Label { .. } | &MemArg::Symbol { .. } => opcode_ril, _ => unreachable!(), }; let mem = mem.pretty_print_default(); format!("{}{} {}, {}", mem_str, op.unwrap(), rn, mem) } &Inst::CmpRSImm16 { op, rn, imm } => { let op = match op { CmpOp::CmpS32 => "chi", CmpOp::CmpS64 => "cghi", _ => unreachable!(), }; let rn = pretty_print_reg(rn); format!("{op} {rn}, {imm}") } &Inst::CmpRSImm32 { op, rn, imm } => { let op = match op { CmpOp::CmpS32 => "cfi", CmpOp::CmpS64 => "cgfi", _ => unreachable!(), }; let rn = pretty_print_reg(rn); format!("{op} {rn}, {imm}") } &Inst::CmpRUImm32 { op, rn, imm } => { let op = match op { CmpOp::CmpL32 => "clfi", CmpOp::CmpL64 => "clgfi", _ => unreachable!(), }; let rn = pretty_print_reg(rn); format!("{op} {rn}, {imm}") } &Inst::CmpTrapRR { op, rn, rm, cond, .. } => { let op = match op { CmpOp::CmpS32 => "crt", CmpOp::CmpS64 => "cgrt", CmpOp::CmpL32 => "clrt", CmpOp::CmpL64 => "clgrt", _ => unreachable!(), }; let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); let cond = cond.pretty_print_default(); format!("{op}{cond} {rn}, {rm}") } &Inst::CmpTrapRSImm16 { op, rn, imm, cond, .. } => { let op = match op { CmpOp::CmpS32 => "cit", CmpOp::CmpS64 => "cgit", _ => unreachable!(), }; let rn = pretty_print_reg(rn); let cond = cond.pretty_print_default(); format!("{op}{cond} {rn}, {imm}") } &Inst::CmpTrapRUImm16 { op, rn, imm, cond, .. } => { let op = match op { CmpOp::CmpL32 => "clfit", CmpOp::CmpL64 => "clgit", _ => unreachable!(), }; let rn = pretty_print_reg(rn); let cond = cond.pretty_print_default(); format!("{op}{cond} {rn}, {imm}") } &Inst::AtomicRmw { alu_op, rd, rn, ref mem, } => { let op = match alu_op { ALUOp::Add32 => "laa", ALUOp::Add64 => "laag", ALUOp::AddLogical32 => "laal", ALUOp::AddLogical64 => "laalg", ALUOp::And32 => "lan", ALUOp::And64 => "lang", ALUOp::Orr32 => "lao", ALUOp::Orr64 => "laog", ALUOp::Xor32 => "lax", ALUOp::Xor64 => "laxg", _ => unreachable!(), }; let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: false, have_d20: true, have_pcrel: false, have_unaligned_pcrel: false, have_index: false, }, ); let mem = mem.pretty_print_default(); format!("{mem_str}{op} {rd}, {rn}, {mem}") } &Inst::AtomicCas32 { rd, ri, rn, ref mem, } | &Inst::AtomicCas64 { rd, ri, rn, ref mem, } => { let (opcode_rs, opcode_rsy) = match self { &Inst::AtomicCas32 { .. } => (Some("cs"), Some("csy")), &Inst::AtomicCas64 { .. } => (None, Some("csg")), _ => unreachable!(), }; let rd = pretty_print_reg_mod(rd, ri); let rn = pretty_print_reg(rn); let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: opcode_rs.is_some(), have_d20: opcode_rsy.is_some(), have_pcrel: false, have_unaligned_pcrel: false, have_index: false, }, ); let op = match &mem { &MemArg::BXD12 { .. } => opcode_rs, &MemArg::BXD20 { .. } => opcode_rsy, _ => unreachable!(), }; let mem = mem.pretty_print_default(); format!("{}{} {}, {}, {}", mem_str, op.unwrap(), rd, rn, mem) } &Inst::Fence => "bcr 14, 0".to_string(), &Inst::Load32 { rd, ref mem } | &Inst::Load32ZExt8 { rd, ref mem } | &Inst::Load32SExt8 { rd, ref mem } | &Inst::Load32ZExt16 { rd, ref mem } | &Inst::Load32SExt16 { rd, ref mem } | &Inst::Load64 { rd, ref mem } | &Inst::Load64ZExt8 { rd, ref mem } | &Inst::Load64SExt8 { rd, ref mem } | &Inst::Load64ZExt16 { rd, ref mem } | &Inst::Load64SExt16 { rd, ref mem } | &Inst::Load64ZExt32 { rd, ref mem } | &Inst::Load64SExt32 { rd, ref mem } | &Inst::LoadRev16 { rd, ref mem } | &Inst::LoadRev32 { rd, ref mem } | &Inst::LoadRev64 { rd, ref mem } => { let (opcode_rx, opcode_rxy, opcode_ril) = match self { &Inst::Load32 { .. } => (Some("l"), Some("ly"), Some("lrl")), &Inst::Load32ZExt8 { .. } => (None, Some("llc"), None), &Inst::Load32SExt8 { .. } => (None, Some("lb"), None), &Inst::Load32ZExt16 { .. } => (None, Some("llh"), Some("llhrl")), &Inst::Load32SExt16 { .. } => (Some("lh"), Some("lhy"), Some("lhrl")), &Inst::Load64 { .. } => (None, Some("lg"), Some("lgrl")), &Inst::Load64ZExt8 { .. } => (None, Some("llgc"), None), &Inst::Load64SExt8 { .. } => (None, Some("lgb"), None), &Inst::Load64ZExt16 { .. } => (None, Some("llgh"), Some("llghrl")), &Inst::Load64SExt16 { .. } => (None, Some("lgh"), Some("lghrl")), &Inst::Load64ZExt32 { .. } => (None, Some("llgf"), Some("llgfrl")), &Inst::Load64SExt32 { .. } => (None, Some("lgf"), Some("lgfrl")), &Inst::LoadRev16 { .. } => (None, Some("lrvh"), None), &Inst::LoadRev32 { .. } => (None, Some("lrv"), None), &Inst::LoadRev64 { .. } => (None, Some("lrvg"), None), _ => unreachable!(), }; let rd = pretty_print_reg(rd.to_reg()); let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: opcode_rx.is_some(), have_d20: opcode_rxy.is_some(), have_pcrel: opcode_ril.is_some(), have_unaligned_pcrel: false, have_index: true, }, ); let op = match &mem { &MemArg::BXD12 { .. } => opcode_rx, &MemArg::BXD20 { .. } => opcode_rxy, &MemArg::Label { .. } | &MemArg::Symbol { .. } => opcode_ril, _ => unreachable!(), }; let mem = mem.pretty_print_default(); format!("{}{} {}, {}", mem_str, op.unwrap(), rd, mem) } &Inst::Store8 { rd, ref mem } | &Inst::Store16 { rd, ref mem } | &Inst::Store32 { rd, ref mem } | &Inst::Store64 { rd, ref mem } | &Inst::StoreRev16 { rd, ref mem } | &Inst::StoreRev32 { rd, ref mem } | &Inst::StoreRev64 { rd, ref mem } => { let (opcode_rx, opcode_rxy, opcode_ril) = match self { &Inst::Store8 { .. } => (Some("stc"), Some("stcy"), None), &Inst::Store16 { .. } => (Some("sth"), Some("sthy"), Some("sthrl")), &Inst::Store32 { .. } => (Some("st"), Some("sty"), Some("strl")), &Inst::Store64 { .. } => (None, Some("stg"), Some("stgrl")), &Inst::StoreRev16 { .. } => (None, Some("strvh"), None), &Inst::StoreRev32 { .. } => (None, Some("strv"), None), &Inst::StoreRev64 { .. } => (None, Some("strvg"), None), _ => unreachable!(), }; let rd = pretty_print_reg(rd); let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: opcode_rx.is_some(), have_d20: opcode_rxy.is_some(), have_pcrel: opcode_ril.is_some(), have_unaligned_pcrel: false, have_index: true, }, ); let op = match &mem { &MemArg::BXD12 { .. } => opcode_rx, &MemArg::BXD20 { .. } => opcode_rxy, &MemArg::Label { .. } | &MemArg::Symbol { .. } => opcode_ril, _ => unreachable!(), }; let mem = mem.pretty_print_default(); format!("{}{} {}, {}", mem_str, op.unwrap(), rd, mem) } &Inst::StoreImm8 { imm, ref mem } => { let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: true, have_d20: true, have_pcrel: false, have_unaligned_pcrel: false, have_index: false, }, ); let op = match &mem { &MemArg::BXD12 { .. } => "mvi", &MemArg::BXD20 { .. } => "mviy", _ => unreachable!(), }; let mem = mem.pretty_print_default(); format!("{mem_str}{op} {mem}, {imm}") } &Inst::StoreImm16 { imm, ref mem } | &Inst::StoreImm32SExt16 { imm, ref mem } | &Inst::StoreImm64SExt16 { imm, ref mem } => { let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: false, have_d20: true, have_pcrel: false, have_unaligned_pcrel: false, have_index: false, }, ); let op = match self { &Inst::StoreImm16 { .. } => "mvhhi", &Inst::StoreImm32SExt16 { .. } => "mvhi", &Inst::StoreImm64SExt16 { .. } => "mvghi", _ => unreachable!(), }; let mem = mem.pretty_print_default(); format!("{mem_str}{op} {mem}, {imm}") } &Inst::LoadMultiple64 { rt, rt2, ref mem } => { let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: false, have_d20: true, have_pcrel: false, have_unaligned_pcrel: false, have_index: false, }, ); let rt = pretty_print_reg(rt.to_reg()); let rt2 = pretty_print_reg(rt2.to_reg()); let mem = mem.pretty_print_default(); format!("{mem_str}lmg {rt}, {rt2}, {mem}") } &Inst::StoreMultiple64 { rt, rt2, ref mem } => { let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: false, have_d20: true, have_pcrel: false, have_unaligned_pcrel: false, have_index: false, }, ); let rt = pretty_print_reg(rt); let rt2 = pretty_print_reg(rt2); let mem = mem.pretty_print_default(); format!("{mem_str}stmg {rt}, {rt2}, {mem}") } &Inst::Mov64 { rd, rm } => { let rd = pretty_print_reg(rd.to_reg()); let rm = pretty_print_reg(rm); format!("lgr {rd}, {rm}") } &Inst::MovPReg { rd, rm } => { let rd = pretty_print_reg(rd.to_reg()); let rm = show_reg(rm.into()); format!("lgr {rd}, {rm}") } &Inst::Mov32 { rd, rm } => { let rd = pretty_print_reg(rd.to_reg()); let rm = pretty_print_reg(rm); format!("lr {rd}, {rm}") } &Inst::Mov32Imm { rd, ref imm } => { let rd = pretty_print_reg(rd.to_reg()); format!("iilf {rd}, {imm}") } &Inst::Mov32SImm16 { rd, ref imm } => { let rd = pretty_print_reg(rd.to_reg()); format!("lhi {rd}, {imm}") } &Inst::Mov64SImm16 { rd, ref imm } => { let rd = pretty_print_reg(rd.to_reg()); format!("lghi {rd}, {imm}") } &Inst::Mov64SImm32 { rd, ref imm } => { let rd = pretty_print_reg(rd.to_reg()); format!("lgfi {rd}, {imm}") } &Inst::Mov64UImm16Shifted { rd, ref imm } => { let rd = pretty_print_reg(rd.to_reg()); let op = match imm.shift { 0 => "llill", 1 => "llilh", 2 => "llihl", 3 => "llihh", _ => unreachable!(), }; format!("{} {}, {}", op, rd, imm.bits) } &Inst::Mov64UImm32Shifted { rd, ref imm } => { let rd = pretty_print_reg(rd.to_reg()); let op = match imm.shift { 0 => "llilf", 1 => "llihf", _ => unreachable!(), }; format!("{} {}, {}", op, rd, imm.bits) } &Inst::Insert64UImm16Shifted { rd, ri, ref imm } => { let rd = pretty_print_reg_mod(rd, ri); let op = match imm.shift { 0 => "iill", 1 => "iilh", 2 => "iihl", 3 => "iihh", _ => unreachable!(), }; format!("{} {}, {}", op, rd, imm.bits) } &Inst::Insert64UImm32Shifted { rd, ri, ref imm } => { let rd = pretty_print_reg_mod(rd, ri); let op = match imm.shift { 0 => "iilf", 1 => "iihf", _ => unreachable!(), }; format!("{} {}, {}", op, rd, imm.bits) } &Inst::LoadAR { rd, ar } => { let rd = pretty_print_reg(rd.to_reg()); format!("ear {rd}, %a{ar}") } &Inst::InsertAR { rd, ri, ar } => { let rd = pretty_print_reg_mod(rd, ri); format!("ear {rd}, %a{ar}") } &Inst::CMov32 { rd, cond, ri, rm } => { let rd = pretty_print_reg_mod(rd, ri); let rm = pretty_print_reg(rm); let cond = cond.pretty_print_default(); format!("locr{cond} {rd}, {rm}") } &Inst::CMov64 { rd, cond, ri, rm } => { let rd = pretty_print_reg_mod(rd, ri); let rm = pretty_print_reg(rm); let cond = cond.pretty_print_default(); format!("locgr{cond} {rd}, {rm}") } &Inst::CMov32SImm16 { rd, cond, ri, ref imm, } => { let rd = pretty_print_reg_mod(rd, ri); let cond = cond.pretty_print_default(); format!("lochi{cond} {rd}, {imm}") } &Inst::CMov64SImm16 { rd, cond, ri, ref imm, } => { let rd = pretty_print_reg_mod(rd, ri); let cond = cond.pretty_print_default(); format!("locghi{cond} {rd}, {imm}") } &Inst::FpuMove32 { rd, rn } => { let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let (rn, rn_fpr) = pretty_print_fpr(rn); if rd_fpr.is_some() && rn_fpr.is_some() { format!("ler {}, {}", rd_fpr.unwrap(), rn_fpr.unwrap()) } else { format!("vlr {rd}, {rn}") } } &Inst::FpuMove64 { rd, rn } => { let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let (rn, rn_fpr) = pretty_print_fpr(rn); if rd_fpr.is_some() && rn_fpr.is_some() { format!("ldr {}, {}", rd_fpr.unwrap(), rn_fpr.unwrap()) } else { format!("vlr {rd}, {rn}") } } &Inst::FpuCMov32 { rd, cond, rm, .. } => { let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let (rm, rm_fpr) = pretty_print_fpr(rm); if rd_fpr.is_some() && rm_fpr.is_some() { let cond = cond.invert().pretty_print_default(); format!("j{} 6 ; ler {}, {}", cond, rd_fpr.unwrap(), rm_fpr.unwrap()) } else { let cond = cond.invert().pretty_print_default(); format!("j{cond} 10 ; vlr {rd}, {rm}") } } &Inst::FpuCMov64 { rd, cond, rm, .. } => { let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let (rm, rm_fpr) = pretty_print_fpr(rm); if rd_fpr.is_some() && rm_fpr.is_some() { let cond = cond.invert().pretty_print_default(); format!("j{} 6 ; ldr {}, {}", cond, rd_fpr.unwrap(), rm_fpr.unwrap()) } else { let cond = cond.invert().pretty_print_default(); format!("j{cond} 10 ; vlr {rd}, {rm}") } } &Inst::FpuRR { fpu_op, rd, rn } => { let (op, op_fpr) = match fpu_op { FPUOp1::Abs32 => ("wflpsb", Some("lpebr")), FPUOp1::Abs64 => ("wflpdb", Some("lpdbr")), FPUOp1::Abs32x4 => ("vflpsb", None), FPUOp1::Abs64x2 => ("vflpdb", None), FPUOp1::Neg32 => ("wflcsb", Some("lcebr")), FPUOp1::Neg64 => ("wflcdb", Some("lcdbr")), FPUOp1::Neg32x4 => ("vflcsb", None), FPUOp1::Neg64x2 => ("vflcdb", None), FPUOp1::NegAbs32 => ("wflnsb", Some("lnebr")), FPUOp1::NegAbs64 => ("wflndb", Some("lndbr")), FPUOp1::NegAbs32x4 => ("vflnsb", None), FPUOp1::NegAbs64x2 => ("vflndb", None), FPUOp1::Sqrt32 => ("wfsqsb", Some("sqebr")), FPUOp1::Sqrt64 => ("wfsqdb", Some("sqdbr")), FPUOp1::Sqrt32x4 => ("vfsqsb", None), FPUOp1::Sqrt64x2 => ("vfsqdb", None), FPUOp1::Cvt32To64 => ("wldeb", Some("ldebr")), FPUOp1::Cvt32x4To64x2 => ("vldeb", None), }; let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let (rn, rn_fpr) = pretty_print_fpr(rn); if op_fpr.is_some() && rd_fpr.is_some() && rn_fpr.is_some() { format!( "{} {}, {}", op_fpr.unwrap(), rd_fpr.unwrap(), rn_fpr.unwrap() ) } else if op.starts_with('w') { format!("{} {}, {}", op, rd_fpr.unwrap_or(rd), rn_fpr.unwrap_or(rn)) } else { format!("{op} {rd}, {rn}") } } &Inst::FpuRRR { fpu_op, rd, rn, rm } => { let (op, opt_m6, op_fpr) = match fpu_op { FPUOp2::Add32 => ("wfasb", "", Some("aebr")), FPUOp2::Add64 => ("wfadb", "", Some("adbr")), FPUOp2::Add32x4 => ("vfasb", "", None), FPUOp2::Add64x2 => ("vfadb", "", None), FPUOp2::Sub32 => ("wfssb", "", Some("sebr")), FPUOp2::Sub64 => ("wfsdb", "", Some("sdbr")), FPUOp2::Sub32x4 => ("vfssb", "", None), FPUOp2::Sub64x2 => ("vfsdb", "", None), FPUOp2::Mul32 => ("wfmsb", "", Some("meebr")), FPUOp2::Mul64 => ("wfmdb", "", Some("mdbr")), FPUOp2::Mul32x4 => ("vfmsb", "", None), FPUOp2::Mul64x2 => ("vfmdb", "", None), FPUOp2::Div32 => ("wfdsb", "", Some("debr")), FPUOp2::Div64 => ("wfddb", "", Some("ddbr")), FPUOp2::Div32x4 => ("vfdsb", "", None), FPUOp2::Div64x2 => ("vfddb", "", None), FPUOp2::Max32 => ("wfmaxsb", ", 1", None), FPUOp2::Max64 => ("wfmaxdb", ", 1", None), FPUOp2::Max32x4 => ("vfmaxsb", ", 1", None), FPUOp2::Max64x2 => ("vfmaxdb", ", 1", None), FPUOp2::Min32 => ("wfminsb", ", 1", None), FPUOp2::Min64 => ("wfmindb", ", 1", None), FPUOp2::Min32x4 => ("vfminsb", ", 1", None), FPUOp2::Min64x2 => ("vfmindb", ", 1", None), FPUOp2::MaxPseudo32 => ("wfmaxsb", ", 3", None), FPUOp2::MaxPseudo64 => ("wfmaxdb", ", 3", None), FPUOp2::MaxPseudo32x4 => ("vfmaxsb", ", 3", None), FPUOp2::MaxPseudo64x2 => ("vfmaxdb", ", 3", None), FPUOp2::MinPseudo32 => ("wfminsb", ", 3", None), FPUOp2::MinPseudo64 => ("wfmindb", ", 3", None), FPUOp2::MinPseudo32x4 => ("vfminsb", ", 3", None), FPUOp2::MinPseudo64x2 => ("vfmindb", ", 3", None), }; let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let (rn, rn_fpr) = pretty_print_fpr(rn); let (rm, rm_fpr) = pretty_print_fpr(rm); if op_fpr.is_some() && rd == rn && rd_fpr.is_some() && rm_fpr.is_some() { format!( "{} {}, {}", op_fpr.unwrap(), rd_fpr.unwrap(), rm_fpr.unwrap() ) } else if op.starts_with('w') { format!( "{} {}, {}, {}{}", op, rd_fpr.unwrap_or(rd), rn_fpr.unwrap_or(rn), rm_fpr.unwrap_or(rm), opt_m6 ) } else { format!("{op} {rd}, {rn}, {rm}{opt_m6}") } } &Inst::FpuRRRR { fpu_op, rd, rn, rm, ra, } => { let (op, op_fpr) = match fpu_op { FPUOp3::MAdd32 => ("wfmasb", Some("maebr")), FPUOp3::MAdd64 => ("wfmadb", Some("madbr")), FPUOp3::MAdd32x4 => ("vfmasb", None), FPUOp3::MAdd64x2 => ("vfmadb", None), FPUOp3::MSub32 => ("wfmssb", Some("msebr")), FPUOp3::MSub64 => ("wfmsdb", Some("msdbr")), FPUOp3::MSub32x4 => ("vfmssb", None), FPUOp3::MSub64x2 => ("vfmsdb", None), }; let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let (rn, rn_fpr) = pretty_print_fpr(rn); let (rm, rm_fpr) = pretty_print_fpr(rm); let (ra, ra_fpr) = pretty_print_fpr(ra); if op_fpr.is_some() && rd == ra && rd_fpr.is_some() && rn_fpr.is_some() && rm_fpr.is_some() { format!( "{} {}, {}, {}", op_fpr.unwrap(), rd_fpr.unwrap(), rn_fpr.unwrap(), rm_fpr.unwrap() ) } else if op.starts_with('w') { format!( "{} {}, {}, {}, {}", op, rd_fpr.unwrap_or(rd), rn_fpr.unwrap_or(rn), rm_fpr.unwrap_or(rm), ra_fpr.unwrap_or(ra) ) } else { format!("{op} {rd}, {rn}, {rm}, {ra}") } } &Inst::FpuCmp32 { rn, rm } => { let (rn, rn_fpr) = pretty_print_fpr(rn); let (rm, rm_fpr) = pretty_print_fpr(rm); if rn_fpr.is_some() && rm_fpr.is_some() { format!("cebr {}, {}", rn_fpr.unwrap(), rm_fpr.unwrap()) } else { format!("wfcsb {}, {}", rn_fpr.unwrap_or(rn), rm_fpr.unwrap_or(rm)) } } &Inst::FpuCmp64 { rn, rm } => { let (rn, rn_fpr) = pretty_print_fpr(rn); let (rm, rm_fpr) = pretty_print_fpr(rm); if rn_fpr.is_some() && rm_fpr.is_some() { format!("cdbr {}, {}", rn_fpr.unwrap(), rm_fpr.unwrap()) } else { format!("wfcdb {}, {}", rn_fpr.unwrap_or(rn), rm_fpr.unwrap_or(rm)) } } &Inst::LoadFpuConst32 { rd, const_data } => { let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let tmp = pretty_print_reg(writable_spilltmp_reg().to_reg()); if rd_fpr.is_some() { format!( "bras {}, 8 ; data.f32 {} ; le {}, 0({})", tmp, f32::from_bits(const_data), rd_fpr.unwrap(), tmp ) } else { format!( "bras {}, 8 ; data.f32 {} ; vlef {}, 0({}), 0", tmp, f32::from_bits(const_data), rd, tmp ) } } &Inst::LoadFpuConst64 { rd, const_data } => { let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let tmp = pretty_print_reg(writable_spilltmp_reg().to_reg()); if rd_fpr.is_some() { format!( "bras {}, 12 ; data.f64 {} ; ld {}, 0({})", tmp, f64::from_bits(const_data), rd_fpr.unwrap(), tmp ) } else { format!( "bras {}, 12 ; data.f64 {} ; vleg {}, 0({}), 0", tmp, f64::from_bits(const_data), rd, tmp ) } } &Inst::FpuRound { op, mode, rd, rn } => { let mode = match mode { FpuRoundMode::Current => 0, FpuRoundMode::ToNearest => 1, FpuRoundMode::ShorterPrecision => 3, FpuRoundMode::ToNearestTiesToEven => 4, FpuRoundMode::ToZero => 5, FpuRoundMode::ToPosInfinity => 6, FpuRoundMode::ToNegInfinity => 7, }; let (opcode, opcode_fpr) = match op { FpuRoundOp::Cvt64To32 => ("wledb", Some("ledbra")), FpuRoundOp::Cvt64x2To32x4 => ("vledb", None), FpuRoundOp::Round32 => ("wfisb", Some("fiebr")), FpuRoundOp::Round64 => ("wfidb", Some("fidbr")), FpuRoundOp::Round32x4 => ("vfisb", None), FpuRoundOp::Round64x2 => ("vfidb", None), FpuRoundOp::ToSInt32 => ("wcfeb", None), FpuRoundOp::ToSInt64 => ("wcgdb", None), FpuRoundOp::ToUInt32 => ("wclfeb", None), FpuRoundOp::ToUInt64 => ("wclgdb", None), FpuRoundOp::ToSInt32x4 => ("vcfeb", None), FpuRoundOp::ToSInt64x2 => ("vcgdb", None), FpuRoundOp::ToUInt32x4 => ("vclfeb", None), FpuRoundOp::ToUInt64x2 => ("vclgdb", None), FpuRoundOp::FromSInt32 => ("wcefb", None), FpuRoundOp::FromSInt64 => ("wcdgb", None), FpuRoundOp::FromUInt32 => ("wcelfb", None), FpuRoundOp::FromUInt64 => ("wcdlgb", None), FpuRoundOp::FromSInt32x4 => ("vcefb", None), FpuRoundOp::FromSInt64x2 => ("vcdgb", None), FpuRoundOp::FromUInt32x4 => ("vcelfb", None), FpuRoundOp::FromUInt64x2 => ("vcdlgb", None), }; let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let (rn, rn_fpr) = pretty_print_fpr(rn); if opcode_fpr.is_some() && rd_fpr.is_some() && rn_fpr.is_some() { format!( "{} {}, {}, {}{}", opcode_fpr.unwrap(), rd_fpr.unwrap(), mode, rn_fpr.unwrap(), if opcode_fpr.unwrap().ends_with('a') { ", 0" } else { "" } ) } else if opcode.starts_with('w') { format!( "{} {}, {}, 0, {}", opcode, rd_fpr.unwrap_or(rd), rn_fpr.unwrap_or(rn), mode ) } else { format!("{opcode} {rd}, {rn}, 0, {mode}") } } &Inst::VecRRR { op, rd, rn, rm } => { let op = match op { VecBinaryOp::Add8x16 => "vab", VecBinaryOp::Add16x8 => "vah", VecBinaryOp::Add32x4 => "vaf", VecBinaryOp::Add64x2 => "vag", VecBinaryOp::Add128 => "vaq", VecBinaryOp::Sub8x16 => "vsb", VecBinaryOp::Sub16x8 => "vsh", VecBinaryOp::Sub32x4 => "vsf", VecBinaryOp::Sub64x2 => "vsg", VecBinaryOp::Sub128 => "vsq", VecBinaryOp::Mul8x16 => "vmlb", VecBinaryOp::Mul16x8 => "vmlhw", VecBinaryOp::Mul32x4 => "vmlf", VecBinaryOp::UMulHi8x16 => "vmlhb", VecBinaryOp::UMulHi16x8 => "vmlhh", VecBinaryOp::UMulHi32x4 => "vmlhf", VecBinaryOp::SMulHi8x16 => "vmhb", VecBinaryOp::SMulHi16x8 => "vmhh", VecBinaryOp::SMulHi32x4 => "vmhf", VecBinaryOp::UMulEven8x16 => "vmleb", VecBinaryOp::UMulEven16x8 => "vmleh", VecBinaryOp::UMulEven32x4 => "vmlef", VecBinaryOp::SMulEven8x16 => "vmeb", VecBinaryOp::SMulEven16x8 => "vmeh", VecBinaryOp::SMulEven32x4 => "vmef", VecBinaryOp::UMulOdd8x16 => "vmlob", VecBinaryOp::UMulOdd16x8 => "vmloh", VecBinaryOp::UMulOdd32x4 => "vmlof", VecBinaryOp::SMulOdd8x16 => "vmob", VecBinaryOp::SMulOdd16x8 => "vmoh", VecBinaryOp::SMulOdd32x4 => "vmof", VecBinaryOp::UMax8x16 => "vmxlb", VecBinaryOp::UMax16x8 => "vmxlh", VecBinaryOp::UMax32x4 => "vmxlf", VecBinaryOp::UMax64x2 => "vmxlg", VecBinaryOp::SMax8x16 => "vmxb", VecBinaryOp::SMax16x8 => "vmxh", VecBinaryOp::SMax32x4 => "vmxf", VecBinaryOp::SMax64x2 => "vmxg", VecBinaryOp::UMin8x16 => "vmnlb", VecBinaryOp::UMin16x8 => "vmnlh", VecBinaryOp::UMin32x4 => "vmnlf", VecBinaryOp::UMin64x2 => "vmnlg", VecBinaryOp::SMin8x16 => "vmnb", VecBinaryOp::SMin16x8 => "vmnh", VecBinaryOp::SMin32x4 => "vmnf", VecBinaryOp::SMin64x2 => "vmng", VecBinaryOp::UAvg8x16 => "vavglb", VecBinaryOp::UAvg16x8 => "vavglh", VecBinaryOp::UAvg32x4 => "vavglf", VecBinaryOp::UAvg64x2 => "vavglg", VecBinaryOp::SAvg8x16 => "vavgb", VecBinaryOp::SAvg16x8 => "vavgh", VecBinaryOp::SAvg32x4 => "vavgf", VecBinaryOp::SAvg64x2 => "vavgg", VecBinaryOp::And128 => "vn", VecBinaryOp::Orr128 => "vo", VecBinaryOp::Xor128 => "vx", VecBinaryOp::NotAnd128 => "vnn", VecBinaryOp::NotOrr128 => "vno", VecBinaryOp::NotXor128 => "vnx", VecBinaryOp::AndNot128 => "vnc", VecBinaryOp::OrrNot128 => "voc", VecBinaryOp::BitPermute128 => "vbperm", VecBinaryOp::LShLByByte128 => "vslb", VecBinaryOp::LShRByByte128 => "vsrlb", VecBinaryOp::AShRByByte128 => "vsrab", VecBinaryOp::LShLByBit128 => "vsl", VecBinaryOp::LShRByBit128 => "vsrl", VecBinaryOp::AShRByBit128 => "vsra", VecBinaryOp::Pack16x8 => "vpkh", VecBinaryOp::Pack32x4 => "vpkf", VecBinaryOp::Pack64x2 => "vpkg", VecBinaryOp::PackUSat16x8 => "vpklsh", VecBinaryOp::PackUSat32x4 => "vpklsf", VecBinaryOp::PackUSat64x2 => "vpklsg", VecBinaryOp::PackSSat16x8 => "vpksh", VecBinaryOp::PackSSat32x4 => "vpksf", VecBinaryOp::PackSSat64x2 => "vpksg", VecBinaryOp::MergeLow8x16 => "vmrlb", VecBinaryOp::MergeLow16x8 => "vmrlh", VecBinaryOp::MergeLow32x4 => "vmrlf", VecBinaryOp::MergeLow64x2 => "vmrlg", VecBinaryOp::MergeHigh8x16 => "vmrhb", VecBinaryOp::MergeHigh16x8 => "vmrhh", VecBinaryOp::MergeHigh32x4 => "vmrhf", VecBinaryOp::MergeHigh64x2 => "vmrhg", }; let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); format!("{op} {rd}, {rn}, {rm}") } &Inst::VecRR { op, rd, rn } => { let op = match op { VecUnaryOp::Abs8x16 => "vlpb", VecUnaryOp::Abs16x8 => "vlph", VecUnaryOp::Abs32x4 => "vlpf", VecUnaryOp::Abs64x2 => "vlpg", VecUnaryOp::Neg8x16 => "vlcb", VecUnaryOp::Neg16x8 => "vlch", VecUnaryOp::Neg32x4 => "vlcf", VecUnaryOp::Neg64x2 => "vlcg", VecUnaryOp::Popcnt8x16 => "vpopctb", VecUnaryOp::Popcnt16x8 => "vpopcth", VecUnaryOp::Popcnt32x4 => "vpopctf", VecUnaryOp::Popcnt64x2 => "vpopctg", VecUnaryOp::Clz8x16 => "vclzb", VecUnaryOp::Clz16x8 => "vclzh", VecUnaryOp::Clz32x4 => "vclzf", VecUnaryOp::Clz64x2 => "vclzg", VecUnaryOp::Ctz8x16 => "vctzb", VecUnaryOp::Ctz16x8 => "vctzh", VecUnaryOp::Ctz32x4 => "vctzf", VecUnaryOp::Ctz64x2 => "vctzg", VecUnaryOp::UnpackULow8x16 => "vupllb", VecUnaryOp::UnpackULow16x8 => "vupllh", VecUnaryOp::UnpackULow32x4 => "vupllf", VecUnaryOp::UnpackUHigh8x16 => "vuplhb", VecUnaryOp::UnpackUHigh16x8 => "vuplhh", VecUnaryOp::UnpackUHigh32x4 => "vuplhf", VecUnaryOp::UnpackSLow8x16 => "vuplb", VecUnaryOp::UnpackSLow16x8 => "vuplh", VecUnaryOp::UnpackSLow32x4 => "vuplf", VecUnaryOp::UnpackSHigh8x16 => "vuphb", VecUnaryOp::UnpackSHigh16x8 => "vuphh", VecUnaryOp::UnpackSHigh32x4 => "vuphf", }; let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); format!("{op} {rd}, {rn}") } &Inst::VecShiftRR { shift_op, rd, rn, shift_imm, shift_reg, } => { let op = match shift_op { VecShiftOp::RotL8x16 => "verllb", VecShiftOp::RotL16x8 => "verllh", VecShiftOp::RotL32x4 => "verllf", VecShiftOp::RotL64x2 => "verllg", VecShiftOp::LShL8x16 => "veslb", VecShiftOp::LShL16x8 => "veslh", VecShiftOp::LShL32x4 => "veslf", VecShiftOp::LShL64x2 => "veslg", VecShiftOp::LShR8x16 => "vesrlb", VecShiftOp::LShR16x8 => "vesrlh", VecShiftOp::LShR32x4 => "vesrlf", VecShiftOp::LShR64x2 => "vesrlg", VecShiftOp::AShR8x16 => "vesrab", VecShiftOp::AShR16x8 => "vesrah", VecShiftOp::AShR32x4 => "vesraf", VecShiftOp::AShR64x2 => "vesrag", }; let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let shift_reg = if shift_reg != zero_reg() { format!("({})", pretty_print_reg(shift_reg)) } else { "".to_string() }; format!("{op} {rd}, {rn}, {shift_imm}{shift_reg}") } &Inst::VecSelect { rd, rn, rm, ra } => { let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); let ra = pretty_print_reg(ra); format!("vsel {rd}, {rn}, {rm}, {ra}") } &Inst::VecPermute { rd, rn, rm, ra } => { let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); let ra = pretty_print_reg(ra); format!("vperm {rd}, {rn}, {rm}, {ra}") } &Inst::VecPermuteDWImm { rd, rn, rm, idx1, idx2, } => { let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); let m4 = (idx1 & 1) * 4 + (idx2 & 1); format!("vpdi {rd}, {rn}, {rm}, {m4}") } &Inst::VecIntCmp { op, rd, rn, rm } | &Inst::VecIntCmpS { op, rd, rn, rm } => { let op = match op { VecIntCmpOp::CmpEq8x16 => "vceqb", VecIntCmpOp::CmpEq16x8 => "vceqh", VecIntCmpOp::CmpEq32x4 => "vceqf", VecIntCmpOp::CmpEq64x2 => "vceqg", VecIntCmpOp::SCmpHi8x16 => "vchb", VecIntCmpOp::SCmpHi16x8 => "vchh", VecIntCmpOp::SCmpHi32x4 => "vchf", VecIntCmpOp::SCmpHi64x2 => "vchg", VecIntCmpOp::UCmpHi8x16 => "vchlb", VecIntCmpOp::UCmpHi16x8 => "vchlh", VecIntCmpOp::UCmpHi32x4 => "vchlf", VecIntCmpOp::UCmpHi64x2 => "vchlg", }; let s = match self { &Inst::VecIntCmp { .. } => "", &Inst::VecIntCmpS { .. } => "s", _ => unreachable!(), }; let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); format!("{op}{s} {rd}, {rn}, {rm}") } &Inst::VecFloatCmp { op, rd, rn, rm } | &Inst::VecFloatCmpS { op, rd, rn, rm } => { let op = match op { VecFloatCmpOp::CmpEq32x4 => "vfcesb", VecFloatCmpOp::CmpEq64x2 => "vfcedb", VecFloatCmpOp::CmpHi32x4 => "vfchsb", VecFloatCmpOp::CmpHi64x2 => "vfchdb", VecFloatCmpOp::CmpHiEq32x4 => "vfchesb", VecFloatCmpOp::CmpHiEq64x2 => "vfchedb", }; let s = match self { &Inst::VecFloatCmp { .. } => "", &Inst::VecFloatCmpS { .. } => "s", _ => unreachable!(), }; let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); format!("{op}{s} {rd}, {rn}, {rm}") } &Inst::VecInt128SCmpHi { tmp, rn, rm } | &Inst::VecInt128UCmpHi { tmp, rn, rm } => { let op = match self { &Inst::VecInt128SCmpHi { .. } => "vecg", &Inst::VecInt128UCmpHi { .. } => "veclg", _ => unreachable!(), }; let tmp = pretty_print_reg(tmp.to_reg()); let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); format!("{op} {rm}, {rn} ; jne 10 ; vchlgs {tmp}, {rn}, {rm}") } &Inst::VecLoad { rd, ref mem } | &Inst::VecLoadRev { rd, ref mem } | &Inst::VecLoadByte16Rev { rd, ref mem } | &Inst::VecLoadByte32Rev { rd, ref mem } | &Inst::VecLoadByte64Rev { rd, ref mem } | &Inst::VecLoadElt16Rev { rd, ref mem } | &Inst::VecLoadElt32Rev { rd, ref mem } | &Inst::VecLoadElt64Rev { rd, ref mem } => { let opcode = match self { &Inst::VecLoad { .. } => "vl", &Inst::VecLoadRev { .. } => "vlbrq", &Inst::VecLoadByte16Rev { .. } => "vlbrh", &Inst::VecLoadByte32Rev { .. } => "vlbrf", &Inst::VecLoadByte64Rev { .. } => "vlbrg", &Inst::VecLoadElt16Rev { .. } => "vlerh", &Inst::VecLoadElt32Rev { .. } => "vlerf", &Inst::VecLoadElt64Rev { .. } => "vlerg", _ => unreachable!(), }; let rd = pretty_print_reg(rd.to_reg()); let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: true, have_d20: false, have_pcrel: false, have_unaligned_pcrel: false, have_index: true, }, ); let mem = mem.pretty_print_default(); format!("{mem_str}{opcode} {rd}, {mem}") } &Inst::VecStore { rd, ref mem } | &Inst::VecStoreRev { rd, ref mem } | &Inst::VecStoreByte16Rev { rd, ref mem } | &Inst::VecStoreByte32Rev { rd, ref mem } | &Inst::VecStoreByte64Rev { rd, ref mem } | &Inst::VecStoreElt16Rev { rd, ref mem } | &Inst::VecStoreElt32Rev { rd, ref mem } | &Inst::VecStoreElt64Rev { rd, ref mem } => { let opcode = match self { &Inst::VecStore { .. } => "vst", &Inst::VecStoreRev { .. } => "vstbrq", &Inst::VecStoreByte16Rev { .. } => "vstbrh", &Inst::VecStoreByte32Rev { .. } => "vstbrf", &Inst::VecStoreByte64Rev { .. } => "vstbrg", &Inst::VecStoreElt16Rev { .. } => "vsterh", &Inst::VecStoreElt32Rev { .. } => "vsterf", &Inst::VecStoreElt64Rev { .. } => "vsterg", _ => unreachable!(), }; let rd = pretty_print_reg(rd); let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: true, have_d20: false, have_pcrel: false, have_unaligned_pcrel: false, have_index: true, }, ); let mem = mem.pretty_print_default(); format!("{mem_str}{opcode} {rd}, {mem}") } &Inst::VecLoadReplicate { size, rd, ref mem } | &Inst::VecLoadReplicateRev { size, rd, ref mem } => { let opcode = match (self, size) { (&Inst::VecLoadReplicate { .. }, 8) => "vlrepb", (&Inst::VecLoadReplicate { .. }, 16) => "vlreph", (&Inst::VecLoadReplicate { .. }, 32) => "vlrepf", (&Inst::VecLoadReplicate { .. }, 64) => "vlrepg", (&Inst::VecLoadReplicateRev { .. }, 16) => "vlbrreph", (&Inst::VecLoadReplicateRev { .. }, 32) => "vlbrrepf", (&Inst::VecLoadReplicateRev { .. }, 64) => "vlbrrepg", _ => unreachable!(), }; let rd = pretty_print_reg(rd.to_reg()); let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: true, have_d20: false, have_pcrel: false, have_unaligned_pcrel: false, have_index: true, }, ); let mem = mem.pretty_print_default(); format!("{mem_str}{opcode} {rd}, {mem}") } &Inst::VecMov { rd, rn } => { let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); format!("vlr {rd}, {rn}") } &Inst::VecCMov { rd, cond, ri, rm } => { let rd = pretty_print_reg_mod(rd, ri); let rm = pretty_print_reg(rm); let cond = cond.invert().pretty_print_default(); format!("j{cond} 10 ; vlr {rd}, {rm}") } &Inst::MovToVec128 { rd, rn, rm } => { let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let rm = pretty_print_reg(rm); format!("vlvgp {rd}, {rn}, {rm}") } &Inst::VecLoadConst { rd, const_data } => { let rd = pretty_print_reg(rd.to_reg()); let tmp = pretty_print_reg(writable_spilltmp_reg().to_reg()); format!("bras {tmp}, 20 ; data.u128 0x{const_data:032x} ; vl {rd}, 0({tmp})") } &Inst::VecLoadConstReplicate { size, rd, const_data, } => { let rd = pretty_print_reg(rd.to_reg()); let tmp = pretty_print_reg(writable_spilltmp_reg().to_reg()); let (opcode, data) = match size { 32 => ("vlrepf", format!("0x{:08x}", const_data as u32)), 64 => ("vlrepg", format!("0x{const_data:016x}")), _ => unreachable!(), }; format!( "bras {}, {} ; data.u{} {} ; {} {}, 0({})", tmp, 4 + size / 8, size, data, opcode, rd, tmp ) } &Inst::VecImmByteMask { rd, mask } => { let rd = pretty_print_reg(rd.to_reg()); format!("vgbm {rd}, {mask}") } &Inst::VecImmBitMask { size, rd, start_bit, end_bit, } => { let rd = pretty_print_reg(rd.to_reg()); let op = match size { 8 => "vgmb", 16 => "vgmh", 32 => "vgmf", 64 => "vgmg", _ => unreachable!(), }; format!("{op} {rd}, {start_bit}, {end_bit}") } &Inst::VecImmReplicate { size, rd, imm } => { let rd = pretty_print_reg(rd.to_reg()); let op = match size { 8 => "vrepib", 16 => "vrepih", 32 => "vrepif", 64 => "vrepig", _ => unreachable!(), }; format!("{op} {rd}, {imm}") } &Inst::VecLoadLane { size, rd, ref mem, lane_imm, .. } | &Inst::VecLoadLaneRev { size, rd, ref mem, lane_imm, .. } => { let opcode_vrx = match (self, size) { (&Inst::VecLoadLane { .. }, 8) => "vleb", (&Inst::VecLoadLane { .. }, 16) => "vleh", (&Inst::VecLoadLane { .. }, 32) => "vlef", (&Inst::VecLoadLane { .. }, 64) => "vleg", (&Inst::VecLoadLaneRev { .. }, 16) => "vlebrh", (&Inst::VecLoadLaneRev { .. }, 32) => "vlebrf", (&Inst::VecLoadLaneRev { .. }, 64) => "vlebrg", _ => unreachable!(), }; let (rd, _) = pretty_print_fpr(rd.to_reg()); let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: true, have_d20: false, have_pcrel: false, have_unaligned_pcrel: false, have_index: true, }, ); let mem = mem.pretty_print_default(); format!("{mem_str}{opcode_vrx} {rd}, {mem}, {lane_imm}") } &Inst::VecLoadLaneUndef { size, rd, ref mem, lane_imm, } | &Inst::VecLoadLaneRevUndef { size, rd, ref mem, lane_imm, } => { let (opcode_vrx, opcode_rx, opcode_rxy) = match (self, size) { (&Inst::VecLoadLaneUndef { .. }, 8) => ("vleb", None, None), (&Inst::VecLoadLaneUndef { .. }, 16) => ("vleh", None, None), (&Inst::VecLoadLaneUndef { .. }, 32) => ("vlef", Some("le"), Some("ley")), (&Inst::VecLoadLaneUndef { .. }, 64) => ("vleg", Some("ld"), Some("ldy")), (&Inst::VecLoadLaneRevUndef { .. }, 16) => ("vlebrh", None, None), (&Inst::VecLoadLaneRevUndef { .. }, 32) => ("vlebrf", None, None), (&Inst::VecLoadLaneRevUndef { .. }, 64) => ("vlebrg", None, None), _ => unreachable!(), }; let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let mem = mem.clone(); if lane_imm == 0 && rd_fpr.is_some() && opcode_rx.is_some() { let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: true, have_d20: true, have_pcrel: false, have_unaligned_pcrel: false, have_index: true, }, ); let op = match &mem { &MemArg::BXD12 { .. } => opcode_rx, &MemArg::BXD20 { .. } => opcode_rxy, _ => unreachable!(), }; let mem = mem.pretty_print_default(); format!("{}{} {}, {}", mem_str, op.unwrap(), rd_fpr.unwrap(), mem) } else { let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: true, have_d20: false, have_pcrel: false, have_unaligned_pcrel: false, have_index: true, }, ); let mem = mem.pretty_print_default(); format!("{mem_str}{opcode_vrx} {rd}, {mem}, {lane_imm}") } } &Inst::VecStoreLane { size, rd, ref mem, lane_imm, } | &Inst::VecStoreLaneRev { size, rd, ref mem, lane_imm, } => { let (opcode_vrx, opcode_rx, opcode_rxy) = match (self, size) { (&Inst::VecStoreLane { .. }, 8) => ("vsteb", None, None), (&Inst::VecStoreLane { .. }, 16) => ("vsteh", None, None), (&Inst::VecStoreLane { .. }, 32) => ("vstef", Some("ste"), Some("stey")), (&Inst::VecStoreLane { .. }, 64) => ("vsteg", Some("std"), Some("stdy")), (&Inst::VecStoreLaneRev { .. }, 16) => ("vstebrh", None, None), (&Inst::VecStoreLaneRev { .. }, 32) => ("vstebrf", None, None), (&Inst::VecStoreLaneRev { .. }, 64) => ("vstebrg", None, None), _ => unreachable!(), }; let (rd, rd_fpr) = pretty_print_fpr(rd); let mem = mem.clone(); if lane_imm == 0 && rd_fpr.is_some() && opcode_rx.is_some() { let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: true, have_d20: true, have_pcrel: false, have_unaligned_pcrel: false, have_index: true, }, ); let op = match &mem { &MemArg::BXD12 { .. } => opcode_rx, &MemArg::BXD20 { .. } => opcode_rxy, _ => unreachable!(), }; let mem = mem.pretty_print_default(); format!("{}{} {}, {}", mem_str, op.unwrap(), rd_fpr.unwrap(), mem) } else { let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: true, have_d20: false, have_pcrel: false, have_unaligned_pcrel: false, have_index: true, }, ); let mem = mem.pretty_print_default(); format!("{mem_str}{opcode_vrx} {rd}, {mem}, {lane_imm}",) } } &Inst::VecInsertLane { size, rd, ri, rn, lane_imm, lane_reg, } => { let op = match size { 8 => "vlvgb", 16 => "vlvgh", 32 => "vlvgf", 64 => "vlvgg", _ => unreachable!(), }; let rd = pretty_print_reg_mod(rd, ri); let rn = pretty_print_reg(rn); let lane_reg = if lane_reg != zero_reg() { format!("({})", pretty_print_reg(lane_reg)) } else { "".to_string() }; format!("{op} {rd}, {rn}, {lane_imm}{lane_reg}") } &Inst::VecInsertLaneUndef { size, rd, rn, lane_imm, lane_reg, } => { let (opcode_vrs, opcode_rre) = match size { 8 => ("vlvgb", None), 16 => ("vlvgh", None), 32 => ("vlvgf", None), 64 => ("vlvgg", Some("ldgr")), _ => unreachable!(), }; let (rd, rd_fpr) = pretty_print_fpr(rd.to_reg()); let rn = pretty_print_reg(rn); let lane_reg = if lane_reg != zero_reg() { format!("({})", pretty_print_reg(lane_reg)) } else { "".to_string() }; if opcode_rre.is_some() && lane_imm == 0 && lane_reg.is_empty() && rd_fpr.is_some() { format!("{} {}, {}", opcode_rre.unwrap(), rd_fpr.unwrap(), rn) } else { format!("{opcode_vrs} {rd}, {rn}, {lane_imm}{lane_reg}") } } &Inst::VecExtractLane { size, rd, rn, lane_imm, lane_reg, } => { let (opcode_vrs, opcode_rre) = match size { 8 => ("vlgvb", None), 16 => ("vlgvh", None), 32 => ("vlgvf", None), 64 => ("vlgvg", Some("lgdr")), _ => unreachable!(), }; let rd = pretty_print_reg(rd.to_reg()); let (rn, rn_fpr) = pretty_print_fpr(rn); let lane_reg = if lane_reg != zero_reg() { format!("({})", pretty_print_reg(lane_reg)) } else { "".to_string() }; if opcode_rre.is_some() && lane_imm == 0 && lane_reg.is_empty() && rn_fpr.is_some() { format!("{} {}, {}", opcode_rre.unwrap(), rd, rn_fpr.unwrap()) } else { format!("{opcode_vrs} {rd}, {rn}, {lane_imm}{lane_reg}") } } &Inst::VecInsertLaneImm { size, rd, ri, imm, lane_imm, } => { let op = match size { 8 => "vleib", 16 => "vleih", 32 => "vleif", 64 => "vleig", _ => unreachable!(), }; let rd = pretty_print_reg_mod(rd, ri); format!("{op} {rd}, {imm}, {lane_imm}") } &Inst::VecReplicateLane { size, rd, rn, lane_imm, } => { let op = match size { 8 => "vrepb", 16 => "vreph", 32 => "vrepf", 64 => "vrepg", _ => unreachable!(), }; let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); format!("{op} {rd}, {rn}, {lane_imm}") } &Inst::Extend { rd, rn, signed, from_bits, to_bits, } => { let rd = pretty_print_reg(rd.to_reg()); let rn = pretty_print_reg(rn); let op = match (signed, from_bits, to_bits) { (_, 1, 32) => "llcr", (_, 1, 64) => "llgcr", (false, 8, 32) => "llcr", (false, 8, 64) => "llgcr", (true, 8, 32) => "lbr", (true, 8, 64) => "lgbr", (false, 16, 32) => "llhr", (false, 16, 64) => "llghr", (true, 16, 32) => "lhr", (true, 16, 64) => "lghr", (false, 32, 64) => "llgfr", (true, 32, 64) => "lgfr", _ => panic!("Unsupported Extend case: {self:?}"), }; format!("{op} {rd}, {rn}") } &Inst::AllocateArgs { size } => { if let Ok(size) = i16::try_from(size) { format!("aghi {}, {}", show_reg(stack_reg()), -size) } else { format!("slgfi {}, {}", show_reg(stack_reg()), size) } } &Inst::Call { link, ref info } => { let link = link.to_reg(); let callee_pop_size = if info.callee_pop_size > 0 { format!(" ; callee_pop_size {}", info.callee_pop_size) } else { "".to_string() }; format!( "brasl {}, {}{}", show_reg(link), info.dest.display(None), callee_pop_size ) } &Inst::CallInd { link, ref info, .. } => { let link = link.to_reg(); let rn = pretty_print_reg(info.dest); let callee_pop_size = if info.callee_pop_size > 0 { format!(" ; callee_pop_size {}", info.callee_pop_size) } else { "".to_string() }; format!("basr {}, {}{}", show_reg(link), rn, callee_pop_size) } &Inst::ReturnCall { ref info } => { let callee_pop_size = if info.callee_pop_size > 0 { format!(" ; callee_pop_size {}", info.callee_pop_size) } else { "".to_string() }; format!("return_call {}{}", info.dest.display(None), callee_pop_size) } &Inst::ReturnCallInd { ref info } => { let rn = pretty_print_reg(info.dest); let callee_pop_size = if info.callee_pop_size > 0 { format!(" ; callee_pop_size {}", info.callee_pop_size) } else { "".to_string() }; format!("return_call_ind {rn}{callee_pop_size}") } &Inst::ElfTlsGetOffset { ref symbol, .. } => { let dest = match &**symbol { SymbolReloc::TlsGd { name } => { format!("tls_gdcall:{}", name.display(None)) } _ => unreachable!(), }; format!("brasl {}, {}", show_reg(gpr(14)), dest) } &Inst::Args { ref args } => { let mut s = "args".to_string(); for arg in args { let preg = pretty_print_reg(arg.preg); let def = pretty_print_reg(arg.vreg.to_reg()); write!(&mut s, " {def}={preg}").unwrap(); } s } &Inst::Rets { ref rets } => { let mut s = "rets".to_string(); for ret in rets { let preg = pretty_print_reg(ret.preg); let vreg = pretty_print_reg(ret.vreg); write!(&mut s, " {vreg}={preg}").unwrap(); } s } &Inst::Ret { link } => { let link = show_reg(link); format!("br {link}") } &Inst::Jump { dest } => { let dest = dest.to_string(); format!("jg {dest}") } &Inst::IndirectBr { rn, .. } => { let rn = pretty_print_reg(rn); format!("br {rn}") } &Inst::CondBr { taken, not_taken, cond, } => { let taken = taken.to_string(); let not_taken = not_taken.to_string(); let cond = cond.pretty_print_default(); format!("jg{cond} {taken} ; jg {not_taken}") } &Inst::OneWayCondBr { target, cond } => { let target = target.to_string(); let cond = cond.pretty_print_default(); format!("jg{cond} {target}") } &Inst::Debugtrap => ".word 0x0001 # debugtrap".to_string(), &Inst::Trap { trap_code } => { format!(".word 0x0000 # trap={trap_code}") } &Inst::TrapIf { cond, trap_code } => { let cond = cond.pretty_print_default(); format!("jg{cond} .+2 # trap={trap_code}") } &Inst::JTSequence { ridx, ref targets } => { let ridx = pretty_print_reg(ridx); let rtmp = pretty_print_reg(writable_spilltmp_reg().to_reg()); // The first entry is the default target, which is not emitted // into the jump table, so we skip it here. It is only in the // list so MachTerminator will see the potential target. let jt_entries: String = targets .iter() .skip(1) .map(|label| format!(" {}", label.to_string())) .collect(); format!( concat!( "larl {}, 14 ; ", "agf {}, 0({}, {}) ; ", "br {} ; ", "jt_entries{}" ), rtmp, rtmp, rtmp, ridx, rtmp, jt_entries, ) } &Inst::LoadSymbolReloc { rd, ref symbol_reloc, } => { let rd = pretty_print_reg(rd.to_reg()); let tmp = pretty_print_reg(writable_spilltmp_reg().to_reg()); let symbol = match &**symbol_reloc { SymbolReloc::Absolute { name, offset } => { format!("{} + {}", name.display(None), offset) } SymbolReloc::TlsGd { name } => format!("{}@tlsgd", name.display(None)), }; format!("bras {tmp}, 12 ; data {symbol} ; lg {rd}, 0({tmp})") } &Inst::LoadAddr { rd, ref mem } => { let rd = pretty_print_reg(rd.to_reg()); let mem = mem.clone(); let (mem_str, mem) = mem_finalize_for_show( &mem, state, MemInstType { have_d12: true, have_d20: true, have_pcrel: true, have_unaligned_pcrel: true, have_index: true, }, ); let op = match &mem { &MemArg::BXD12 { .. } => "la", &MemArg::BXD20 { .. } => "lay", &MemArg::Label { .. } | &MemArg::Symbol { .. } => "larl", _ => unreachable!(), }; let mem = mem.pretty_print_default(); format!("{mem_str}{op} {rd}, {mem}") } &Inst::StackProbeLoop { probe_count, guard_size, } => { let probe_count = pretty_print_reg(probe_count.to_reg()); let stack_reg = pretty_print_reg(stack_reg()); format!("0: aghi {stack_reg}, -{guard_size} ; mvi 0({stack_reg}), 0 ; brct {probe_count}, 0b") } &Inst::Loop { ref body, cond } => { let body = body .into_iter() .map(|inst| inst.print_with_state(state)) .collect::>() .join(" ; "); let cond = cond.pretty_print_default(); format!("0: {body} ; jg{cond} 0b ; 1:") } &Inst::CondBreak { cond } => { let cond = cond.pretty_print_default(); format!("jg{cond} 1f") } &Inst::Unwind { ref inst } => { format!("unwind {inst:?}") } &Inst::DummyUse { reg } => { let reg = pretty_print_reg(reg); format!("dummy_use {reg}") } } } } //============================================================================= // Label fixups and jump veneers. /// Different forms of label references for different instruction formats. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum LabelUse { #[allow(dead_code)] /// RI-format branch. 16-bit signed offset. PC-relative, offset is imm << 1. BranchRI, /// RIL-format branch. 32-bit signed offset. PC-relative, offset is imm << 1. BranchRIL, /// 32-bit PC relative constant offset (from address of constant itself), /// signed. Used in jump tables. PCRel32, /// 32-bit PC relative constant offset (from address of call instruction), /// signed. Offset is imm << 1. Used for call relocations. PCRel32Dbl, } impl MachInstLabelUse for LabelUse { /// Alignment for veneer code. const ALIGN: CodeOffset = 2; /// Maximum PC-relative range (positive), inclusive. fn max_pos_range(self) -> CodeOffset { match self { // 16-bit signed immediate, left-shifted by 1. LabelUse::BranchRI => ((1 << 15) - 1) << 1, // 32-bit signed immediate, left-shifted by 1. LabelUse::BranchRIL => 0xffff_fffe, // 32-bit signed immediate. LabelUse::PCRel32 => 0x7fff_ffff, // 32-bit signed immediate, left-shifted by 1, offset by 2. LabelUse::PCRel32Dbl => 0xffff_fffc, } } /// Maximum PC-relative range (negative). fn max_neg_range(self) -> CodeOffset { match self { // 16-bit signed immediate, left-shifted by 1. LabelUse::BranchRI => (1 << 15) << 1, // 32-bit signed immediate, left-shifted by 1. // NOTE: This should be 4GB, but CodeOffset is only u32. LabelUse::BranchRIL => 0xffff_ffff, // 32-bit signed immediate. LabelUse::PCRel32 => 0x8000_0000, // 32-bit signed immediate, left-shifted by 1, offset by 2. // NOTE: This should be 4GB + 2, but CodeOffset is only u32. LabelUse::PCRel32Dbl => 0xffff_ffff, } } /// Size of window into code needed to do the patch. fn patch_size(self) -> CodeOffset { match self { LabelUse::BranchRI => 4, LabelUse::BranchRIL => 6, LabelUse::PCRel32 => 4, LabelUse::PCRel32Dbl => 4, } } /// Perform the patch. fn patch(self, buffer: &mut [u8], use_offset: CodeOffset, label_offset: CodeOffset) { let pc_rel = (label_offset as i64) - (use_offset as i64); debug_assert!(pc_rel <= self.max_pos_range() as i64); debug_assert!(pc_rel >= -(self.max_neg_range() as i64)); debug_assert!(pc_rel & 1 == 0); let pc_rel_shifted = pc_rel >> 1; match self { LabelUse::BranchRI => { buffer[2..4].clone_from_slice(&u16::to_be_bytes(pc_rel_shifted as u16)); } LabelUse::BranchRIL => { buffer[2..6].clone_from_slice(&u32::to_be_bytes(pc_rel_shifted as u32)); } LabelUse::PCRel32 => { let insn_word = u32::from_be_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]); let insn_word = insn_word.wrapping_add(pc_rel as u32); buffer[0..4].clone_from_slice(&u32::to_be_bytes(insn_word)); } LabelUse::PCRel32Dbl => { let insn_word = u32::from_be_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]); let insn_word = insn_word.wrapping_add((pc_rel_shifted + 1) as u32); buffer[0..4].clone_from_slice(&u32::to_be_bytes(insn_word)); } } } /// Is a veneer supported for this label reference type? fn supports_veneer(self) -> bool { false } /// How large is the veneer, if supported? fn veneer_size(self) -> CodeOffset { 0 } fn worst_case_veneer_size() -> CodeOffset { 0 } /// Generate a veneer into the buffer, given that this veneer is at `veneer_offset`, and return /// an offset and label-use for the veneer's use of the original label. fn generate_veneer( self, _buffer: &mut [u8], _veneer_offset: CodeOffset, ) -> (CodeOffset, LabelUse) { unreachable!(); } fn from_reloc(reloc: Reloc, addend: Addend) -> Option { match (reloc, addend) { (Reloc::S390xPCRel32Dbl, 2) => Some(LabelUse::PCRel32Dbl), (Reloc::S390xPLTRel32Dbl, 2) => Some(LabelUse::PCRel32Dbl), _ => None, } } }