use super::{address::Address, regs};
use crate::CallingConvention;
use crate::aarch64::regs::zero;
use crate::masm::{
DivKind, Extend, ExtendKind, FloatCmpKind, Imm, IntCmpKind, RemKind, RoundingMode, ShiftKind,
Signed, TRUSTED_FLAGS, TruncKind,
};
use crate::{
constant_pool::ConstantPool,
masm::OperandSize,
reg::{Reg, WritableReg, writable},
};
use cranelift_codegen::PatchRegion;
use cranelift_codegen::isa::aarch64::inst::emit::{enc_arith_rrr, enc_move_wide, enc_movk};
use cranelift_codegen::isa::aarch64::inst::{
ASIMDFPModImm, FpuToIntOp, MoveWideConst, NZCV, UImm5,
};
use cranelift_codegen::{
Final, MachBuffer, MachBufferFinalized, MachInst, MachInstEmit, MachInstEmitState, MachLabel,
Writable,
ir::{ExternalName, MemFlags, SourceLoc, TrapCode, UserExternalNameRef},
isa::aarch64::inst::{
self, ALUOp, ALUOp3, AMode, BitOp, BranchTarget, Cond, CondBrKind, ExtendOp,
FPULeftShiftImm, FPUOp1, FPUOp2,
FPUOpRI::{self, UShr32, UShr64},
FPUOpRIMod, FPURightShiftImm, FpuRoundMode, Imm12, ImmLogic, ImmShift, Inst, IntToFpuOp,
PairAMode, ScalarSize, VecLanesOp, VecMisc2, VectorSize,
emit::{EmitInfo, EmitState},
},
settings,
};
use wasmtime_math::{f32_cvt_to_int_bounds, f64_cvt_to_int_bounds};
impl From<OperandSize> for inst::OperandSize {
fn from(size: OperandSize) -> Self {
match size {
OperandSize::S32 => Self::Size32,
OperandSize::S64 => Self::Size64,
s => panic!("Invalid operand size {s:?}"),
}
}
}
impl From<IntCmpKind> for Cond {
fn from(value: IntCmpKind) -> Self {
match value {
IntCmpKind::Eq => Cond::Eq,
IntCmpKind::Ne => Cond::Ne,
IntCmpKind::LtS => Cond::Lt,
IntCmpKind::LtU => Cond::Lo,
IntCmpKind::GtS => Cond::Gt,
IntCmpKind::GtU => Cond::Hi,
IntCmpKind::LeS => Cond::Le,
IntCmpKind::LeU => Cond::Ls,
IntCmpKind::GeS => Cond::Ge,
IntCmpKind::GeU => Cond::Hs,
}
}
}
impl From<FloatCmpKind> for Cond {
fn from(value: FloatCmpKind) -> Self {
match value {
FloatCmpKind::Eq => Cond::Eq,
FloatCmpKind::Ne => Cond::Ne,
FloatCmpKind::Lt => Cond::Mi,
FloatCmpKind::Gt => Cond::Gt,
FloatCmpKind::Le => Cond::Ls,
FloatCmpKind::Ge => Cond::Ge,
}
}
}
impl From<OperandSize> for ScalarSize {
fn from(size: OperandSize) -> ScalarSize {
match size {
OperandSize::S8 => ScalarSize::Size8,
OperandSize::S16 => ScalarSize::Size16,
OperandSize::S32 => ScalarSize::Size32,
OperandSize::S64 => ScalarSize::Size64,
OperandSize::S128 => ScalarSize::Size128,
}
}
}
pub(crate) struct Assembler {
buffer: MachBuffer<Inst>,
emit_info: EmitInfo,
emit_state: EmitState,
pool: ConstantPool,
}
impl Assembler {
pub fn new(shared_flags: settings::Flags) -> Self {
Self {
buffer: MachBuffer::<Inst>::new(),
emit_state: Default::default(),
emit_info: EmitInfo::new(shared_flags),
pool: ConstantPool::new(),
}
}
}
impl Assembler {
pub fn finalize(mut self, loc: Option<SourceLoc>) -> MachBufferFinalized<Final> {
let stencil = self
.buffer
.finish(&self.pool.constants(), self.emit_state.ctrl_plane_mut());
stencil.apply_base_srcloc(loc.unwrap_or_default())
}
fn emit(&mut self, inst: Inst) {
self.emit_with_island(inst, Inst::worst_case_size());
}
fn emit_with_island(&mut self, inst: Inst, needed_space: u32) {
if self.buffer.island_needed(needed_space) {
let label = self.buffer.get_label();
let jmp = Inst::Jump {
dest: BranchTarget::Label(label),
};
jmp.emit(&mut self.buffer, &self.emit_info, &mut self.emit_state);
self.buffer
.emit_island(needed_space, self.emit_state.ctrl_plane_mut());
self.buffer
.bind_label(label, self.emit_state.ctrl_plane_mut());
}
inst.emit(&mut self.buffer, &self.emit_info, &mut self.emit_state);
}
pub fn load_int_const(&mut self, imm: u64, rd: WritableReg) {
let writable = rd.map(Into::into);
Inst::load_constant(writable, imm, &mut |_| writable)
.into_iter()
.for_each(|i| self.emit(i));
}
pub fn load_fp_const(&mut self, rd: Writable<Reg>, imm: Imm, size: OperandSize) {
let bits = match imm {
Imm::F32(v) => v as u64,
Imm::F64(v) => v,
_ => unreachable!(),
};
match ASIMDFPModImm::maybe_from_u64(bits, size.into()) {
Some(imm) => {
self.emit(Inst::FpuMoveFPImm {
rd: rd.map(Into::into),
imm,
size: size.into(),
});
}
_ => {
let addr = self.add_constant(&imm.to_bytes());
self.uload(addr, rd, size, TRUSTED_FLAGS);
}
}
}
pub fn add_constant(&mut self, constant: &[u8]) -> Address {
let handle = self.pool.register(constant, &mut self.buffer);
Address::constant(handle)
}
pub fn stp(&mut self, xt1: Reg, xt2: Reg, addr: Address) {
let mem: PairAMode = addr.try_into().unwrap();
self.emit(Inst::StoreP64 {
rt: xt1.into(),
rt2: xt2.into(),
mem,
flags: MemFlags::trusted(),
});
}
pub fn str(&mut self, reg: Reg, addr: Address, size: OperandSize, flags: MemFlags) {
let mem: AMode = addr.try_into().unwrap();
use OperandSize::*;
let inst = match (reg.is_int(), size) {
(_, S8) => Inst::Store8 {
rd: reg.into(),
mem,
flags,
},
(_, S16) => Inst::Store16 {
rd: reg.into(),
mem,
flags,
},
(true, S32) => Inst::Store32 {
rd: reg.into(),
mem,
flags,
},
(false, S32) => Inst::FpuStore32 {
rd: reg.into(),
mem,
flags,
},
(true, S64) => Inst::Store64 {
rd: reg.into(),
mem,
flags,
},
(false, S64) => Inst::FpuStore64 {
rd: reg.into(),
mem,
flags,
},
(_, S128) => Inst::FpuStore128 {
rd: reg.into(),
mem,
flags,
},
};
self.emit(inst);
}
pub fn sload(&mut self, addr: Address, rd: WritableReg, size: OperandSize, flags: MemFlags) {
self.ldr(addr, rd, size, true, flags);
}
pub fn uload(&mut self, addr: Address, rd: WritableReg, size: OperandSize, flags: MemFlags) {
self.ldr(addr, rd, size, false, flags);
}
fn ldr(
&mut self,
addr: Address,
rd: WritableReg,
size: OperandSize,
signed: bool,
flags: MemFlags,
) {
use OperandSize::*;
let writable_reg = rd.map(Into::into);
let mem: AMode = addr.try_into().unwrap();
let inst = match (rd.to_reg().is_int(), signed, size) {
(_, false, S8) => Inst::ULoad8 {
rd: writable_reg,
mem,
flags,
},
(_, true, S8) => Inst::SLoad8 {
rd: writable_reg,
mem,
flags,
},
(_, false, S16) => Inst::ULoad16 {
rd: writable_reg,
mem,
flags,
},
(_, true, S16) => Inst::SLoad16 {
rd: writable_reg,
mem,
flags,
},
(true, false, S32) => Inst::ULoad32 {
rd: writable_reg,
mem,
flags,
},
(false, _, S32) => Inst::FpuLoad32 {
rd: writable_reg,
mem,
flags,
},
(true, true, S32) => Inst::SLoad32 {
rd: writable_reg,
mem,
flags,
},
(true, _, S64) => Inst::ULoad64 {
rd: writable_reg,
mem,
flags,
},
(false, _, S64) => Inst::FpuLoad64 {
rd: writable_reg,
mem,
flags,
},
(_, _, S128) => Inst::FpuLoad128 {
rd: writable_reg,
mem,
flags,
},
};
self.emit(inst);
}
pub fn ldp(&mut self, xt1: Reg, xt2: Reg, addr: Address) {
let writable_xt1 = Writable::from_reg(xt1.into());
let writable_xt2 = Writable::from_reg(xt2.into());
let mem = addr.try_into().unwrap();
self.emit(Inst::LoadP64 {
rt: writable_xt1,
rt2: writable_xt2,
mem,
flags: MemFlags::trusted(),
});
}
pub fn mov_rr(&mut self, rm: Reg, rd: WritableReg, size: OperandSize) {
let writable_rd = rd.map(Into::into);
self.emit(Inst::Mov {
size: size.into(),
rd: writable_rd,
rm: rm.into(),
});
}
pub fn fmov_rr(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
let writable = rd.map(Into::into);
let inst = match size {
OperandSize::S32 => Inst::FpuMove32 {
rd: writable,
rn: rn.into(),
},
OperandSize::S64 => Inst::FpuMove64 {
rd: writable,
rn: rn.into(),
},
_ => unreachable!(),
};
self.emit(inst);
}
pub fn mov_to_fpu(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
let writable_rd = rd.map(Into::into);
self.emit(Inst::MovToFpu {
size: size.into(),
rd: writable_rd,
rn: rn.into(),
});
}
pub fn mov_from_vec(&mut self, rn: Reg, rd: WritableReg, idx: u8, size: OperandSize) {
self.emit(Inst::MovFromVec {
rd: rd.map(Into::into),
rn: rn.into(),
idx,
size: size.into(),
});
}
pub fn add_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.alu_rrr_extend(ALUOp::Add, rm, rn, rd, size);
}
pub fn adds_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.alu_rrr_extend(ALUOp::AddS, rm, rn, rd, size);
}
pub fn add_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
self.alu_ir(ALUOp::Add, imm, rn, rd, size)
}
pub fn adds_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
self.alu_ir(ALUOp::AddS, imm, rn, rd, size)
}
fn alu_ir(&mut self, alu_op: ALUOp, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
if let Some(imm) = Imm12::maybe_from_u64(imm) {
self.alu_rri(alu_op, imm, rn, rd, size);
} else {
let scratch = regs::scratch();
self.load_int_const(imm, writable!(scratch));
self.alu_rrr_extend(alu_op, scratch, rn, rd, size);
}
}
pub fn addv(&mut self, rn: Reg, rd: WritableReg, size: VectorSize) {
self.emit(Inst::VecLanes {
op: VecLanesOp::Addv,
rd: rd.map(Into::into),
rn: rn.into(),
size,
});
}
pub fn sub_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.alu_rrr_extend(ALUOp::Sub, rm, rn, rd, size);
}
pub fn sub_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
let alu_op = ALUOp::Sub;
if let Some(imm) = Imm12::maybe_from_u64(imm) {
self.alu_rri(alu_op, imm, rn, rd, size);
} else {
let scratch = regs::scratch();
self.load_int_const(imm, writable!(scratch));
self.alu_rrr_extend(alu_op, scratch, rn, rd, size);
}
}
pub fn subs_rrr(&mut self, rm: Reg, rn: Reg, size: OperandSize) {
self.alu_rrr_extend(ALUOp::SubS, rm, rn, writable!(regs::zero()), size);
}
pub fn subs_ir(&mut self, imm: u64, rn: Reg, size: OperandSize) {
let alu_op = ALUOp::SubS;
if let Some(imm) = Imm12::maybe_from_u64(imm) {
self.alu_rri(alu_op, imm, rn, writable!(regs::zero()), size);
} else {
let scratch = regs::scratch();
self.load_int_const(imm, writable!(scratch));
self.alu_rrr_extend(alu_op, scratch, rn, writable!(regs::zero()), size);
}
}
pub fn mul_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.alu_rrrr(ALUOp3::MAdd, rm, rn, rd, regs::zero(), size);
}
pub fn mul_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
let scratch = regs::scratch();
self.load_int_const(imm, writable!(scratch));
self.alu_rrrr(ALUOp3::MAdd, scratch, rn, rd, regs::zero(), size);
}
pub fn div_rrr(
&mut self,
divisor: Reg,
dividend: Reg,
dest: Writable<Reg>,
kind: DivKind,
size: OperandSize,
) {
self.trapz(divisor, TrapCode::INTEGER_DIVISION_BY_ZERO, size);
if kind == DivKind::Signed {
self.alu_rri(
ALUOp::AddS,
Imm12::maybe_from_u64(1).expect("1 to fit in 12 bits"),
divisor,
writable!(zero()),
size,
);
self.emit(Inst::CCmpImm {
size: size.into(),
rn: dividend.into(),
imm: UImm5::maybe_from_u8(1).expect("1 fits in 5 bits"),
nzcv: NZCV::new(false, false, false, false),
cond: Cond::Eq,
});
self.trapif(Cond::Vs, TrapCode::INTEGER_OVERFLOW);
}
let size = if size == OperandSize::S32 && kind == DivKind::Signed {
self.extend(
divisor,
writable!(divisor),
ExtendKind::Signed(Extend::<Signed>::I64Extend32),
);
self.extend(
dividend,
writable!(dividend),
ExtendKind::Signed(Extend::<Signed>::I64Extend32),
);
OperandSize::S64
} else {
size
};
let op = match kind {
DivKind::Signed => ALUOp::SDiv,
DivKind::Unsigned => ALUOp::UDiv,
};
self.alu_rrr(op, divisor, dividend, dest.map(Into::into), size);
}
pub fn rem_rrr(
&mut self,
divisor: Reg,
dividend: Reg,
dest: Writable<Reg>,
kind: RemKind,
size: OperandSize,
) {
self.trapz(divisor, TrapCode::INTEGER_DIVISION_BY_ZERO, size);
let size = if size == OperandSize::S32 && kind.is_signed() {
self.extend(
divisor,
writable!(divisor),
ExtendKind::Signed(Extend::<Signed>::I64Extend32),
);
self.extend(
dividend,
writable!(dividend),
ExtendKind::Signed(Extend::<Signed>::I64Extend32),
);
OperandSize::S64
} else {
size
};
let op = match kind {
RemKind::Signed => ALUOp::SDiv,
RemKind::Unsigned => ALUOp::UDiv,
};
let scratch = regs::scratch();
self.alu_rrr(op, divisor, dividend, writable!(scratch), size);
self.alu_rrrr(
ALUOp3::MSub,
scratch,
divisor,
dest.map(Into::into),
dividend,
size,
);
}
pub fn and_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.alu_rrr(ALUOp::And, rm, rn, rd, size);
}
pub fn and_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
let alu_op = ALUOp::And;
let cl_size: inst::OperandSize = size.into();
if let Some(imm) = ImmLogic::maybe_from_u64(imm, cl_size.to_ty()) {
self.alu_rri_logic(alu_op, imm, rn, rd, size);
} else {
let scratch = regs::scratch();
self.load_int_const(imm, writable!(scratch));
self.alu_rrr(alu_op, scratch, rn, rd, size);
}
}
pub fn or_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.alu_rrr(ALUOp::Orr, rm, rn, rd, size);
}
pub fn or_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
let alu_op = ALUOp::Orr;
let cl_size: inst::OperandSize = size.into();
if let Some(imm) = ImmLogic::maybe_from_u64(imm, cl_size.to_ty()) {
self.alu_rri_logic(alu_op, imm, rn, rd, size);
} else {
let scratch = regs::scratch();
self.load_int_const(imm, writable!(scratch));
self.alu_rrr(alu_op, scratch, rn, rd, size);
}
}
pub fn xor_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.alu_rrr(ALUOp::Eor, rm, rn, rd, size);
}
pub fn xor_ir(&mut self, imm: u64, rn: Reg, rd: WritableReg, size: OperandSize) {
let alu_op = ALUOp::Eor;
let cl_size: inst::OperandSize = size.into();
if let Some(imm) = ImmLogic::maybe_from_u64(imm, cl_size.to_ty()) {
self.alu_rri_logic(alu_op, imm, rn, rd, size);
} else {
let scratch = regs::scratch();
self.load_int_const(imm, writable!(scratch));
self.alu_rrr(alu_op, scratch, rn, rd, size);
}
}
pub fn shift_rrr(
&mut self,
rm: Reg,
rn: Reg,
rd: WritableReg,
kind: ShiftKind,
size: OperandSize,
) {
let shift_op = self.shift_kind_to_alu_op(kind, rm, size);
self.alu_rrr(shift_op, rm, rn, rd, size);
}
pub fn shift_ir(
&mut self,
imm: u64,
rn: Reg,
rd: WritableReg,
kind: ShiftKind,
size: OperandSize,
) {
let shift_op = self.shift_kind_to_alu_op(kind, rn, size);
if let Some(imm) = ImmShift::maybe_from_u64(imm) {
self.alu_rri_shift(shift_op, imm, rn, rd, size);
} else {
let scratch = regs::scratch();
self.load_int_const(imm, writable!(scratch));
self.alu_rrr(shift_op, scratch, rn, rd, size);
}
}
pub fn clz(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
self.bit_rr(BitOp::Clz, rn, rd, size);
}
pub fn rbit(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
self.bit_rr(BitOp::RBit, rn, rd, size);
}
pub fn fadd_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.fpu_rrr(FPUOp2::Add, rm, rn, rd, size);
}
pub fn fsub_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.fpu_rrr(FPUOp2::Sub, rm, rn, rd, size);
}
pub fn fmul_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.fpu_rrr(FPUOp2::Mul, rm, rn, rd, size);
}
pub fn fdiv_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.fpu_rrr(FPUOp2::Div, rm, rn, rd, size);
}
pub fn fmax_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.fpu_rrr(FPUOp2::Max, rm, rn, rd, size);
}
pub fn fmin_rrr(&mut self, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.fpu_rrr(FPUOp2::Min, rm, rn, rd, size);
}
pub fn fneg_rr(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
self.fpu_rr(FPUOp1::Neg, rn, rd, size);
}
pub fn fabs_rr(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
self.fpu_rr(FPUOp1::Abs, rn, rd, size);
}
pub fn fsqrt_rr(&mut self, rn: Reg, rd: WritableReg, size: OperandSize) {
self.fpu_rr(FPUOp1::Sqrt, rn, rd, size);
}
pub fn fround_rr(&mut self, rn: Reg, rd: WritableReg, mode: RoundingMode, size: OperandSize) {
let fpu_mode = match (mode, size) {
(RoundingMode::Nearest, OperandSize::S32) => FpuRoundMode::Nearest32,
(RoundingMode::Up, OperandSize::S32) => FpuRoundMode::Plus32,
(RoundingMode::Down, OperandSize::S32) => FpuRoundMode::Minus32,
(RoundingMode::Zero, OperandSize::S32) => FpuRoundMode::Zero32,
(RoundingMode::Nearest, OperandSize::S64) => FpuRoundMode::Nearest64,
(RoundingMode::Up, OperandSize::S64) => FpuRoundMode::Plus64,
(RoundingMode::Down, OperandSize::S64) => FpuRoundMode::Minus64,
(RoundingMode::Zero, OperandSize::S64) => FpuRoundMode::Zero64,
(m, o) => panic!("Invalid rounding mode or operand size {m:?}, {o:?}"),
};
self.fpu_round(fpu_mode, rn, rd)
}
pub fn fushr_rri(&mut self, rn: Reg, rd: WritableReg, amount: u8, size: OperandSize) {
let imm = FPURightShiftImm {
amount,
lane_size_in_bits: size.num_bits(),
};
let ushr = match size {
OperandSize::S32 => UShr32(imm),
OperandSize::S64 => UShr64(imm),
_ => unreachable!(),
};
self.fpu_rri(ushr, rn, rd)
}
pub fn fsli_rri_mod(
&mut self,
ri: Reg,
rn: Reg,
rd: WritableReg,
amount: u8,
size: OperandSize,
) {
let imm = FPULeftShiftImm {
amount,
lane_size_in_bits: size.num_bits(),
};
let sli = match size {
OperandSize::S32 => FPUOpRIMod::Sli32(imm),
OperandSize::S64 => FPUOpRIMod::Sli64(imm),
_ => unreachable!(),
};
self.fpu_rri_mod(sli, ri, rn, rd)
}
pub fn fcmp(&mut self, rn: Reg, rm: Reg, size: OperandSize) {
self.emit(Inst::FpuCmp {
size: size.into(),
rn: rn.into(),
rm: rm.into(),
})
}
pub fn cvt_sint_to_float(
&mut self,
rn: Reg,
rd: WritableReg,
src_size: OperandSize,
dst_size: OperandSize,
) {
let op = match (src_size, dst_size) {
(OperandSize::S32, OperandSize::S32) => IntToFpuOp::I32ToF32,
(OperandSize::S64, OperandSize::S32) => IntToFpuOp::I64ToF32,
(OperandSize::S32, OperandSize::S64) => IntToFpuOp::I32ToF64,
(OperandSize::S64, OperandSize::S64) => IntToFpuOp::I64ToF64,
_ => unreachable!(),
};
self.emit(Inst::IntToFpu {
op,
rd: rd.map(Into::into),
rn: rn.into(),
});
}
pub fn cvt_uint_to_float(
&mut self,
rn: Reg,
rd: WritableReg,
src_size: OperandSize,
dst_size: OperandSize,
) {
let op = match (src_size, dst_size) {
(OperandSize::S32, OperandSize::S32) => IntToFpuOp::U32ToF32,
(OperandSize::S64, OperandSize::S32) => IntToFpuOp::U64ToF32,
(OperandSize::S32, OperandSize::S64) => IntToFpuOp::U32ToF64,
(OperandSize::S64, OperandSize::S64) => IntToFpuOp::U64ToF64,
_ => unreachable!(),
};
self.emit(Inst::IntToFpu {
op,
rd: rd.map(Into::into),
rn: rn.into(),
});
}
pub fn cvt_float_to_float(
&mut self,
rn: Reg,
rd: WritableReg,
src_size: OperandSize,
dst_size: OperandSize,
) {
let (fpu_op, size) = match (src_size, dst_size) {
(OperandSize::S32, OperandSize::S64) => (FPUOp1::Cvt32To64, ScalarSize::Size32),
(OperandSize::S64, OperandSize::S32) => (FPUOp1::Cvt64To32, ScalarSize::Size64),
_ => unimplemented!(),
};
self.emit(Inst::FpuRR {
fpu_op,
size,
rd: rd.map(Into::into),
rn: rn.into(),
});
}
pub fn ret(&mut self) {
self.emit(Inst::Ret {});
}
pub fn jmp(&mut self, target: MachLabel) {
self.emit(Inst::Jump {
dest: BranchTarget::Label(target),
});
}
pub fn jmp_if(&mut self, kind: Cond, taken: MachLabel) {
self.emit(Inst::CondBr {
taken: BranchTarget::Label(taken),
not_taken: BranchTarget::ResolvedOffset(4),
kind: CondBrKind::Cond(kind),
});
}
pub fn jmp_table(
&mut self,
targets: &[MachLabel],
default: MachLabel,
index: Reg,
tmp1: Reg,
tmp2: Reg,
) {
self.emit_with_island(
Inst::JTSequence {
default,
targets: Box::new(targets.to_vec()),
ridx: index.into(),
rtmp1: Writable::from_reg(tmp1.into()),
rtmp2: Writable::from_reg(tmp2.into()),
},
(4 * (8 + targets.len())).try_into().unwrap(),
);
}
pub fn cset(&mut self, rd: WritableReg, cond: Cond) {
self.emit(Inst::CSet {
rd: rd.map(Into::into),
cond,
});
}
pub fn csel(&mut self, rn: Reg, rm: Reg, rd: WritableReg, cond: Cond) {
self.emit(Inst::CSel {
rd: rd.map(Into::into),
rn: rn.into(),
rm: rm.into(),
cond,
});
}
pub fn cnt(&mut self, rd: WritableReg) {
self.emit(Inst::VecMisc {
op: VecMisc2::Cnt,
rd: rd.map(Into::into),
rn: rd.to_reg().into(),
size: VectorSize::Size8x8,
});
}
pub fn extend(&mut self, rn: Reg, rd: WritableReg, kind: ExtendKind) {
self.emit(Inst::Extend {
rd: rd.map(Into::into),
rn: rn.into(),
signed: kind.signed(),
from_bits: kind.from_bits(),
to_bits: kind.to_bits(),
})
}
pub fn ands_rr(&mut self, rn: Reg, rm: Reg, size: OperandSize) {
self.alu_rrr(ALUOp::AndS, rm, rn, writable!(regs::zero()), size);
}
pub fn udf(&mut self, code: TrapCode) {
self.emit(Inst::Udf { trap_code: code });
}
pub fn trapif(&mut self, cc: Cond, code: TrapCode) {
self.emit(Inst::TrapIf {
kind: CondBrKind::Cond(cc),
trap_code: code,
});
}
pub fn trapz(&mut self, rn: Reg, code: TrapCode, size: OperandSize) {
self.emit(Inst::TrapIf {
kind: CondBrKind::Zero(rn.into(), size.into()),
trap_code: code,
});
}
fn alu_rri(&mut self, op: ALUOp, imm: Imm12, rn: Reg, rd: WritableReg, size: OperandSize) {
self.emit(Inst::AluRRImm12 {
alu_op: op,
size: size.into(),
rd: rd.map(Into::into),
rn: rn.into(),
imm12: imm,
});
}
fn alu_rri_logic(
&mut self,
op: ALUOp,
imm: ImmLogic,
rn: Reg,
rd: WritableReg,
size: OperandSize,
) {
self.emit(Inst::AluRRImmLogic {
alu_op: op,
size: size.into(),
rd: rd.map(Into::into),
rn: rn.into(),
imml: imm,
});
}
fn alu_rri_shift(
&mut self,
op: ALUOp,
imm: ImmShift,
rn: Reg,
rd: WritableReg,
size: OperandSize,
) {
self.emit(Inst::AluRRImmShift {
alu_op: op,
size: size.into(),
rd: rd.map(Into::into),
rn: rn.into(),
immshift: imm,
});
}
fn alu_rrr(&mut self, op: ALUOp, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.emit(Inst::AluRRR {
alu_op: op,
size: size.into(),
rd: rd.map(Into::into),
rn: rn.into(),
rm: rm.into(),
});
}
fn alu_rrr_extend(&mut self, op: ALUOp, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.emit(Inst::AluRRRExtend {
alu_op: op,
size: size.into(),
rd: rd.map(Into::into),
rn: rn.into(),
rm: rm.into(),
extendop: ExtendOp::UXTX,
});
}
fn alu_rrrr(
&mut self,
op: ALUOp3,
rm: Reg,
rn: Reg,
rd: WritableReg,
ra: Reg,
size: OperandSize,
) {
self.emit(Inst::AluRRRR {
alu_op: op,
size: size.into(),
rd: rd.map(Into::into),
rn: rn.into(),
rm: rm.into(),
ra: ra.into(),
});
}
fn fpu_rrr(&mut self, op: FPUOp2, rm: Reg, rn: Reg, rd: WritableReg, size: OperandSize) {
self.emit(Inst::FpuRRR {
fpu_op: op,
size: size.into(),
rd: rd.map(Into::into),
rn: rn.into(),
rm: rm.into(),
});
}
fn fpu_rri(&mut self, op: FPUOpRI, rn: Reg, rd: WritableReg) {
self.emit(Inst::FpuRRI {
fpu_op: op,
rd: rd.map(Into::into),
rn: rn.into(),
});
}
fn fpu_rri_mod(&mut self, op: FPUOpRIMod, ri: Reg, rn: Reg, rd: WritableReg) {
self.emit(Inst::FpuRRIMod {
fpu_op: op,
rd: rd.map(Into::into),
ri: ri.into(),
rn: rn.into(),
});
}
fn fpu_rr(&mut self, op: FPUOp1, rn: Reg, rd: WritableReg, size: OperandSize) {
self.emit(Inst::FpuRR {
fpu_op: op,
size: size.into(),
rd: rd.map(Into::into),
rn: rn.into(),
});
}
fn fpu_round(&mut self, op: FpuRoundMode, rn: Reg, rd: WritableReg) {
self.emit(Inst::FpuRound {
op: op,
rd: rd.map(Into::into),
rn: rn.into(),
});
}
fn bit_rr(&mut self, op: BitOp, rn: Reg, rd: WritableReg, size: OperandSize) {
self.emit(Inst::BitRR {
op,
size: size.into(),
rd: rd.map(Into::into),
rn: rn.into(),
});
}
fn shift_kind_to_alu_op(&mut self, kind: ShiftKind, r: Reg, size: OperandSize) -> ALUOp {
match kind {
ShiftKind::Shl => ALUOp::Lsl,
ShiftKind::ShrS => ALUOp::Asr,
ShiftKind::ShrU => ALUOp::Lsr,
ShiftKind::Rotr => ALUOp::Extr,
ShiftKind::Rotl => {
self.alu_rrr(ALUOp::Sub, r, regs::zero(), writable!(r), size);
ALUOp::Extr
}
}
}
pub fn get_label(&mut self) -> MachLabel {
self.buffer.get_label()
}
pub fn buffer_mut(&mut self) -> &mut MachBuffer<Inst> {
&mut self.buffer
}
pub fn buffer(&self) -> &MachBuffer<Inst> {
&self.buffer
}
pub fn call_with_name(&mut self, name: UserExternalNameRef, call_conv: CallingConvention) {
self.emit(Inst::Call {
info: Box::new(cranelift_codegen::CallInfo::empty(
ExternalName::user(name),
call_conv.into(),
)),
})
}
pub fn call_with_reg(&mut self, callee: Reg, call_conv: CallingConvention) {
self.emit(Inst::CallInd {
info: Box::new(cranelift_codegen::CallInfo::empty(
callee.into(),
call_conv.into(),
)),
})
}
fn min_fp_value(
&mut self,
signed: bool,
in_size: OperandSize,
out_size: OperandSize,
rd: Writable<Reg>,
) {
match in_size {
OperandSize::S32 => {
let (min, _) = f32_cvt_to_int_bounds(signed, out_size.num_bits().into());
self.load_fp_const(rd, Imm::f32(min.to_bits()), in_size);
}
OperandSize::S64 => {
let (min, _) = f64_cvt_to_int_bounds(signed, out_size.num_bits().into());
self.load_fp_const(rd, Imm::f64(min.to_bits()), in_size);
}
s => unreachable!("unsupported floating-point size: {}bit", s.num_bits()),
};
}
fn max_fp_value(
&mut self,
signed: bool,
in_size: OperandSize,
out_size: OperandSize,
rd: Writable<Reg>,
) {
match in_size {
OperandSize::S32 => {
let (_, max) = f32_cvt_to_int_bounds(signed, out_size.num_bits().into());
self.load_fp_const(rd, Imm::f32(max.to_bits()), in_size);
}
OperandSize::S64 => {
let (_, max) = f64_cvt_to_int_bounds(signed, out_size.num_bits().into());
self.load_fp_const(rd, Imm::f64(max.to_bits()), in_size);
}
s => unreachable!("unsupported floating-point size: {}bit", s.num_bits()),
};
}
fn check_nan(&mut self, rn: Reg, size: OperandSize) {
self.fcmp(rn, rn, size);
self.trapif(Cond::Vs, TrapCode::BAD_CONVERSION_TO_INTEGER);
}
pub fn fpu_to_int(
&mut self,
dst: Writable<Reg>,
src: Reg,
src_size: OperandSize,
dst_size: OperandSize,
kind: TruncKind,
signed: bool,
) {
if kind.is_unchecked() {
self.check_nan(src, src_size);
let tmp_reg = writable!(regs::float_scratch());
self.min_fp_value(signed, src_size, dst_size, tmp_reg);
self.fcmp(src, tmp_reg.to_reg(), src_size);
self.trapif(Cond::Le, TrapCode::INTEGER_OVERFLOW);
self.max_fp_value(signed, src_size, dst_size, tmp_reg);
self.fcmp(src, tmp_reg.to_reg(), src_size);
self.trapif(Cond::Ge, TrapCode::INTEGER_OVERFLOW);
}
self.cvt_fpu_to_int(dst, src, src_size, dst_size, signed)
}
pub fn cvt_fpu_to_int(
&mut self,
dst: Writable<Reg>,
src: Reg,
src_size: OperandSize,
dst_size: OperandSize,
signed: bool,
) {
let op = match (src_size, dst_size, signed) {
(OperandSize::S32, OperandSize::S32, false) => FpuToIntOp::F32ToU32,
(OperandSize::S32, OperandSize::S32, true) => FpuToIntOp::F32ToI32,
(OperandSize::S32, OperandSize::S64, false) => FpuToIntOp::F32ToU64,
(OperandSize::S32, OperandSize::S64, true) => FpuToIntOp::F32ToI64,
(OperandSize::S64, OperandSize::S32, false) => FpuToIntOp::F64ToU32,
(OperandSize::S64, OperandSize::S32, true) => FpuToIntOp::F64ToI32,
(OperandSize::S64, OperandSize::S64, false) => FpuToIntOp::F64ToU64,
(OperandSize::S64, OperandSize::S64, true) => FpuToIntOp::F64ToI64,
(fsize, int_size, signed) => unimplemented!(
"unsupported conversion: f{} to {}{}",
fsize.num_bits(),
if signed { "i" } else { "u" },
int_size.num_bits(),
),
};
self.emit(Inst::FpuToInt {
op,
rd: dst.map(Into::into),
rn: src.into(),
});
}
}
pub(crate) struct PatchableAddToReg {
region: PatchRegion,
reg: Writable<Reg>,
tmp: Writable<Reg>,
}
impl PatchableAddToReg {
pub(crate) fn new(reg: Writable<Reg>, tmp: Writable<Reg>, buf: &mut MachBuffer<Inst>) -> Self {
let insns = Self::add_immediate_instruction_sequence(reg, tmp, 0);
let open = buf.start_patchable();
buf.put_data(&insns);
let region = buf.end_patchable(open);
Self { region, reg, tmp }
}
fn add_immediate_instruction_sequence(
reg: Writable<Reg>,
tmp: Writable<Reg>,
imm: i32,
) -> [u8; 12] {
let imm_hi = imm as u64 & 0xffff_0000;
let imm_hi = MoveWideConst::maybe_from_u64(imm_hi).unwrap();
let imm_lo = imm as u64 & 0x0000_ffff;
let imm_lo = MoveWideConst::maybe_from_u64(imm_lo).unwrap();
let size = OperandSize::S64.into();
let tmp = tmp.map(Into::into);
let rd = reg.map(Into::into);
let mov_insn = enc_move_wide(inst::MoveWideOp::MovZ, tmp, imm_hi, size);
let movk_insn = enc_movk(tmp, imm_lo, size);
let add_bits_31_21: u32 = 0b00001011_000 | (size.sf_bit() << 10);
let add_bits_15_10: u32 = 0;
let add_insn = enc_arith_rrr(
add_bits_31_21,
add_bits_15_10,
rd,
rd.to_reg(),
tmp.to_reg(),
);
let mut buf = [0u8; 12];
buf[0..4].copy_from_slice(&mov_insn.to_le_bytes());
buf[4..8].copy_from_slice(&movk_insn.to_le_bytes());
buf[8..12].copy_from_slice(&add_insn.to_le_bytes());
buf
}
pub(crate) fn finalize(self, val: i32, buffer: &mut MachBuffer<Inst>) {
let insns = Self::add_immediate_instruction_sequence(self.reg, self.tmp, val);
let slice = self.region.patch(buffer);
assert_eq!(slice.len(), insns.len());
slice.copy_from_slice(&insns);
}
}