use crate::{
isa::reg::Reg,
masm::{DivKind, ExtendKind, IntCmpKind, OperandSize, RemKind, RoundingMode, ShiftKind},
};
use cranelift_codegen::{
ir::{
types, ConstantPool, ExternalName, LibCall, MemFlags, SourceLoc, TrapCode,
UserExternalNameRef,
},
isa::{
unwind::UnwindInst,
x64::{
args::{
self, AluRmiROpcode, Amode, CmpOpcode, DivSignedness, ExtMode, FromWritableReg,
Gpr, GprMem, GprMemImm, Imm8Gpr, Imm8Reg, RegMem, RegMemImm,
ShiftKind as CraneliftShiftKind, SseOpcode, SyntheticAmode, WritableGpr,
WritableXmm, Xmm, XmmMem, XmmMemAligned, CC,
},
encoding::rex::{encode_modrm, RexFlags},
settings as x64_settings, EmitInfo, EmitState, Inst,
},
},
settings, Final, MachBuffer, MachBufferFinalized, MachInstEmit, MachInstEmitState, MachLabel,
PatchRegion, RelocDistance, VCodeConstantData, VCodeConstants, Writable,
};
use super::address::Address;
use smallvec::SmallVec;
impl From<Reg> for RegMemImm {
fn from(reg: Reg) -> Self {
RegMemImm::reg(reg.into())
}
}
impl From<Reg> for RegMem {
fn from(value: Reg) -> Self {
RegMem::Reg { reg: value.into() }
}
}
impl From<Reg> for WritableGpr {
fn from(reg: Reg) -> Self {
let writable = Writable::from_reg(reg.into());
WritableGpr::from_writable_reg(writable).expect("valid writable gpr")
}
}
impl From<Reg> for WritableXmm {
fn from(reg: Reg) -> Self {
let writable = Writable::from_reg(reg.into());
WritableXmm::from_writable_reg(writable).expect("valid writable xmm")
}
}
impl From<Reg> for Gpr {
fn from(reg: Reg) -> Self {
Gpr::unwrap_new(reg.into())
}
}
impl From<Reg> for GprMem {
fn from(value: Reg) -> Self {
GprMem::unwrap_new(value.into())
}
}
impl From<Reg> for GprMemImm {
fn from(reg: Reg) -> Self {
GprMemImm::unwrap_new(reg.into())
}
}
impl From<Reg> for Imm8Gpr {
fn from(value: Reg) -> Self {
Imm8Gpr::unwrap_new(Imm8Reg::Reg { reg: value.into() })
}
}
impl From<Reg> for Xmm {
fn from(reg: Reg) -> Self {
Xmm::unwrap_new(reg.into())
}
}
impl From<OperandSize> for args::OperandSize {
fn from(size: OperandSize) -> Self {
match size {
OperandSize::S8 => Self::Size8,
OperandSize::S16 => Self::Size16,
OperandSize::S32 => Self::Size32,
OperandSize::S64 => Self::Size64,
s => panic!("Invalid operand size {:?}", s),
}
}
}
impl From<DivKind> for DivSignedness {
fn from(kind: DivKind) -> DivSignedness {
match kind {
DivKind::Signed => DivSignedness::Signed,
DivKind::Unsigned => DivSignedness::Unsigned,
}
}
}
impl From<IntCmpKind> for CC {
fn from(value: IntCmpKind) -> Self {
match value {
IntCmpKind::Eq => CC::Z,
IntCmpKind::Ne => CC::NZ,
IntCmpKind::LtS => CC::L,
IntCmpKind::LtU => CC::B,
IntCmpKind::GtS => CC::NLE,
IntCmpKind::GtU => CC::NBE,
IntCmpKind::LeS => CC::LE,
IntCmpKind::LeU => CC::BE,
IntCmpKind::GeS => CC::NL,
IntCmpKind::GeU => CC::NB,
}
}
}
impl From<ShiftKind> for CraneliftShiftKind {
fn from(value: ShiftKind) -> Self {
match value {
ShiftKind::Shl => CraneliftShiftKind::ShiftLeft,
ShiftKind::ShrS => CraneliftShiftKind::ShiftRightArithmetic,
ShiftKind::ShrU => CraneliftShiftKind::ShiftRightLogical,
ShiftKind::Rotl => CraneliftShiftKind::RotateLeft,
ShiftKind::Rotr => CraneliftShiftKind::RotateRight,
}
}
}
impl From<ExtendKind> for ExtMode {
fn from(value: ExtendKind) -> Self {
match value {
ExtendKind::I64ExtendI32S | ExtendKind::I64ExtendI32U | ExtendKind::I64Extend32S => {
ExtMode::LQ
}
ExtendKind::I32Extend8S => ExtMode::BL,
ExtendKind::I32Extend16S => ExtMode::WL,
ExtendKind::I64Extend8S => ExtMode::BQ,
ExtendKind::I64Extend16S => ExtMode::WQ,
}
}
}
impl From<OperandSize> for Option<ExtMode> {
fn from(value: OperandSize) -> Self {
use OperandSize::*;
match value {
S128 | S64 => None,
S8 => Some(ExtMode::BQ),
S16 => Some(ExtMode::WQ),
S32 => Some(ExtMode::LQ),
}
}
}
pub(crate) struct Assembler {
buffer: MachBuffer<Inst>,
emit_info: EmitInfo,
emit_state: EmitState,
isa_flags: x64_settings::Flags,
pool: ConstantPool,
constants: VCodeConstants,
}
impl Assembler {
pub fn new(shared_flags: settings::Flags, isa_flags: x64_settings::Flags) -> Self {
Self {
buffer: MachBuffer::<Inst>::new(),
emit_state: Default::default(),
emit_info: EmitInfo::new(shared_flags, isa_flags.clone()),
constants: Default::default(),
pool: ConstantPool::new(),
isa_flags,
}
}
pub fn buffer_mut(&mut self) -> &mut MachBuffer<Inst> {
&mut self.buffer
}
pub fn buffer(&self) -> &MachBuffer<Inst> {
&self.buffer
}
pub fn add_constant(&mut self, constant: &[u8]) -> Address {
let handle = self.pool.insert(constant.into());
Address::constant(handle)
}
pub fn finalize(mut self, loc: Option<SourceLoc>) -> MachBufferFinalized<Final> {
let stencil = self
.buffer
.finish(&self.constants, self.emit_state.ctrl_plane_mut());
stencil.apply_base_srcloc(loc.unwrap_or_default())
}
fn emit(&mut self, inst: Inst) {
inst.emit(&mut self.buffer, &self.emit_info, &mut self.emit_state);
}
fn to_synthetic_amode(
addr: &Address,
pool: &mut ConstantPool,
constants: &mut VCodeConstants,
buffer: &mut MachBuffer<Inst>,
memflags: MemFlags,
) -> SyntheticAmode {
match addr {
Address::Offset { base, offset } => {
let amode = Amode::imm_reg(*offset as i32, (*base).into()).with_flags(memflags);
SyntheticAmode::real(amode)
}
Address::Const(c) => {
let constant_data = pool.get(*c);
let data = VCodeConstantData::Pool(*c, constant_data.clone());
let needs_registration = !constants.pool_uses(&data);
let constant = constants.insert(VCodeConstantData::Pool(*c, constant_data.clone()));
if needs_registration {
buffer.register_constant(&constant, &data);
}
SyntheticAmode::ConstantOffset(constant)
}
}
}
pub fn emit_unwind_inst(&mut self, inst: UnwindInst) {
self.emit(Inst::Unwind { inst })
}
pub fn push_r(&mut self, reg: Reg) {
self.emit(Inst::Push64 { src: reg.into() });
}
pub fn pop_r(&mut self, dst: Reg) {
let writable = Writable::from_reg(dst.into());
let dst = WritableGpr::from_writable_reg(writable).expect("valid writable gpr");
self.emit(Inst::Pop64 { dst });
}
pub fn ret(&mut self) {
self.emit(Inst::Ret {
stack_bytes_to_pop: 0,
});
}
pub fn mov_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::MovRR {
src: src.into(),
dst: dst.into(),
size: size.into(),
});
}
pub fn mov_rm(&mut self, src: Reg, addr: &Address, size: OperandSize, flags: MemFlags) {
assert!(addr.is_offset());
let dst = Self::to_synthetic_amode(
addr,
&mut self.pool,
&mut self.constants,
&mut self.buffer,
flags,
);
self.emit(Inst::MovRM {
size: size.into(),
src: src.into(),
dst,
});
}
pub fn mov_im(&mut self, src: i32, addr: &Address, size: OperandSize, flags: MemFlags) {
assert!(addr.is_offset());
let dst = Self::to_synthetic_amode(
addr,
&mut self.pool,
&mut self.constants,
&mut self.buffer,
flags,
);
self.emit(Inst::MovImmM {
size: size.into(),
simm32: src,
dst,
});
}
pub fn mov_ir(&mut self, imm: u64, dst: Reg, size: OperandSize) {
let dst = WritableGpr::from_writable_reg(Writable::from_reg(dst.into()))
.expect("valid writable gpr");
self.emit(Inst::Imm {
dst_size: size.into(),
simm64: imm,
dst,
});
}
pub fn movzx_mr(&mut self, addr: &Address, dst: Reg, ext: Option<ExtMode>, memflags: MemFlags) {
let src = Self::to_synthetic_amode(
addr,
&mut self.pool,
&mut self.constants,
&mut self.buffer,
memflags,
);
if let Some(ext) = ext {
let reg_mem = RegMem::mem(src);
self.emit(Inst::MovzxRmR {
ext_mode: ext,
src: GprMem::unwrap_new(reg_mem),
dst: dst.into(),
});
} else {
self.emit(Inst::Mov64MR {
src,
dst: dst.into(),
});
}
}
pub fn movsx_mr(
&mut self,
addr: &Address,
dst: Reg,
ext: impl Into<ExtMode>,
memflags: MemFlags,
) {
let src = Self::to_synthetic_amode(
addr,
&mut self.pool,
&mut self.constants,
&mut self.buffer,
memflags,
);
let reg_mem = RegMem::mem(src);
self.emit(Inst::MovsxRmR {
ext_mode: ext.into(),
src: GprMem::unwrap_new(reg_mem),
dst: dst.into(),
})
}
pub fn movzx_rr(&mut self, src: Reg, dst: Reg, kind: ExtendKind) {
self.emit(Inst::MovzxRmR {
ext_mode: kind.into(),
src: src.into(),
dst: dst.into(),
})
}
pub fn movsx_rr(&mut self, src: Reg, dst: Reg, kind: ExtendKind) {
self.emit(Inst::MovsxRmR {
ext_mode: kind.into(),
src: src.into(),
dst: dst.into(),
});
}
pub fn cmov(&mut self, src: Reg, dst: Reg, cc: IntCmpKind, size: OperandSize) {
self.emit(Inst::Cmove {
size: size.into(),
cc: cc.into(),
consequent: src.into(),
alternative: dst.into(),
dst: dst.into(),
})
}
pub fn xmm_mov_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
use OperandSize::*;
let op = match size {
S32 => SseOpcode::Movaps,
S64 => SseOpcode::Movapd,
S128 => SseOpcode::Movdqa,
S8 | S16 => unreachable!(),
};
self.emit(Inst::XmmUnaryRmRUnaligned {
op,
src: XmmMem::unwrap_new(src.into()),
dst: dst.into(),
});
}
pub fn xmm_mov_mr(&mut self, src: &Address, dst: Reg, size: OperandSize, flags: MemFlags) {
use OperandSize::*;
assert!(dst.is_float());
let op = match size {
S32 => SseOpcode::Movss,
S64 => SseOpcode::Movsd,
S128 => SseOpcode::Movdqu,
S16 | S8 => unreachable!(),
};
let src = Self::to_synthetic_amode(
src,
&mut self.pool,
&mut self.constants,
&mut self.buffer,
flags,
);
self.emit(Inst::XmmUnaryRmRUnaligned {
op,
src: XmmMem::unwrap_new(RegMem::mem(src)),
dst: dst.into(),
});
}
pub fn xmm_mov_rm(&mut self, src: Reg, dst: &Address, size: OperandSize, flags: MemFlags) {
use OperandSize::*;
assert!(src.is_float());
let op = match size {
S32 => SseOpcode::Movss,
S64 => SseOpcode::Movsd,
S128 => SseOpcode::Movdqu,
S16 | S8 => unreachable!(),
};
let dst = Self::to_synthetic_amode(
dst,
&mut self.pool,
&mut self.constants,
&mut self.buffer,
flags,
);
self.emit(Inst::XmmMovRM {
op,
src: src.into(),
dst,
});
}
pub fn xmm_cmov(&mut self, src: Reg, dst: Reg, cc: IntCmpKind, size: OperandSize) {
let ty = match size {
OperandSize::S32 => types::F32,
OperandSize::S64 => types::F64,
OperandSize::S128 => types::I128,
OperandSize::S8 | OperandSize::S16 => unreachable!(),
};
self.emit(Inst::XmmCmove {
ty,
cc: cc.into(),
consequent: Xmm::unwrap_new(src.into()),
alternative: dst.into(),
dst: dst.into(),
})
}
pub fn sub_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::Sub,
src1: dst.into(),
src2: src.into(),
dst: dst.into(),
});
}
pub fn sub_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
let imm = RegMemImm::imm(imm as u32);
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::Sub,
src1: dst.into(),
src2: GprMemImm::unwrap_new(imm),
dst: dst.into(),
});
}
pub fn and_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::And,
src1: dst.into(),
src2: src.into(),
dst: dst.into(),
});
}
pub fn and_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
let imm = RegMemImm::imm(imm as u32);
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::And,
src1: dst.into(),
src2: GprMemImm::unwrap_new(imm),
dst: dst.into(),
});
}
pub fn xmm_and_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Andps,
OperandSize::S64 => SseOpcode::Andpd,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::XmmRmR {
op,
src1: dst.into(),
src2: XmmMemAligned::from(Xmm::from(src)),
dst: dst.into(),
});
}
pub fn xmm_andn_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Andnps,
OperandSize::S64 => SseOpcode::Andnpd,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::XmmRmR {
op,
src1: dst.into(),
src2: Xmm::from(src).into(),
dst: dst.into(),
});
}
pub fn gpr_to_xmm(&mut self, src: Reg, dst: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Movd,
OperandSize::S64 => SseOpcode::Movq,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::GprToXmm {
op,
src: src.into(),
dst: dst.into(),
src_size: size.into(),
})
}
pub fn xmm_to_gpr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Movd,
OperandSize::S64 => SseOpcode::Movq,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::XmmToGpr {
op,
src: src.into(),
dst: dst.into(),
dst_size: size.into(),
});
}
pub fn cvt_float_to_sint_seq(
&mut self,
src: Reg,
dst: Reg,
tmp_gpr: Reg,
tmp_xmm: Reg,
src_size: OperandSize,
dst_size: OperandSize,
saturating: bool,
) {
self.emit(Inst::CvtFloatToSintSeq {
dst_size: dst_size.into(),
src_size: src_size.into(),
is_saturating: saturating,
src: src.into(),
dst: dst.into(),
tmp_gpr: tmp_gpr.into(),
tmp_xmm: tmp_xmm.into(),
});
}
pub fn cvt_float_to_uint_seq(
&mut self,
src: Reg,
dst: Reg,
tmp_gpr: Reg,
tmp_xmm: Reg,
tmp_xmm2: Reg,
src_size: OperandSize,
dst_size: OperandSize,
saturating: bool,
) {
self.emit(Inst::CvtFloatToUintSeq {
dst_size: dst_size.into(),
src_size: src_size.into(),
is_saturating: saturating,
src: src.into(),
dst: dst.into(),
tmp_gpr: tmp_gpr.into(),
tmp_xmm: tmp_xmm.into(),
tmp_xmm2: tmp_xmm2.into(),
});
}
pub fn cvt_sint_to_float(
&mut self,
src: Reg,
dst: Reg,
src_size: OperandSize,
dst_size: OperandSize,
) {
let op = match dst_size {
OperandSize::S32 => SseOpcode::Cvtsi2ss,
OperandSize::S64 => SseOpcode::Cvtsi2sd,
OperandSize::S16 | OperandSize::S8 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::CvtIntToFloat {
op,
src1: dst.into(),
src2: src.into(),
dst: dst.into(),
src2_size: src_size.into(),
});
}
pub fn cvt_uint64_to_float_seq(
&mut self,
src: Reg,
dst: Reg,
tmp_gpr1: Reg,
tmp_gpr2: Reg,
dst_size: OperandSize,
) {
self.emit(Inst::CvtUint64ToFloatSeq {
dst_size: dst_size.into(),
src: src.into(),
dst: dst.into(),
tmp_gpr1: tmp_gpr1.into(),
tmp_gpr2: tmp_gpr2.into(),
});
}
pub fn cvt_float_to_float(
&mut self,
src: Reg,
dst: Reg,
src_size: OperandSize,
dst_size: OperandSize,
) {
let op = match (src_size, dst_size) {
(OperandSize::S32, OperandSize::S64) => SseOpcode::Cvtss2sd,
(OperandSize::S64, OperandSize::S32) => SseOpcode::Cvtsd2ss,
_ => unimplemented!(),
};
self.emit(Inst::XmmRmRUnaligned {
op,
src2: Xmm::unwrap_new(src.into()).into(),
src1: dst.into(),
dst: dst.into(),
});
}
pub fn or_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::Or,
src1: dst.into(),
src2: src.into(),
dst: dst.into(),
});
}
pub fn or_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
let imm = RegMemImm::imm(imm as u32);
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::Or,
src1: dst.into(),
src2: GprMemImm::unwrap_new(imm),
dst: dst.into(),
});
}
pub fn xmm_or_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Orps,
OperandSize::S64 => SseOpcode::Orpd,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::XmmRmR {
op,
src1: dst.into(),
src2: XmmMemAligned::from(Xmm::from(src)),
dst: dst.into(),
});
}
pub fn xor_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::Xor,
src1: dst.into(),
src2: src.into(),
dst: dst.into(),
});
}
pub fn xor_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
let imm = RegMemImm::imm(imm as u32);
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::Xor,
src1: dst.into(),
src2: GprMemImm::unwrap_new(imm),
dst: dst.into(),
});
}
pub fn xmm_xor_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Xorps,
OperandSize::S64 => SseOpcode::Xorpd,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::XmmRmR {
op,
src1: dst.into(),
src2: XmmMemAligned::from(Xmm::from(src)),
dst: dst.into(),
});
}
pub fn shift_rr(&mut self, src: Reg, dst: Reg, kind: ShiftKind, size: OperandSize) {
self.emit(Inst::ShiftR {
size: size.into(),
kind: kind.into(),
src: dst.into(),
num_bits: src.into(),
dst: dst.into(),
});
}
pub fn shift_ir(&mut self, imm: u8, dst: Reg, kind: ShiftKind, size: OperandSize) {
let imm = imm.into();
self.emit(Inst::ShiftR {
size: size.into(),
kind: kind.into(),
src: dst.into(),
num_bits: Imm8Gpr::unwrap_new(imm),
dst: dst.into(),
});
}
pub fn div(&mut self, divisor: Reg, dst: (Reg, Reg), kind: DivKind, size: OperandSize) {
let trap = match kind {
DivKind::Signed => {
self.emit(Inst::CmpRmiR {
size: size.into(),
src1: divisor.into(),
src2: GprMemImm::unwrap_new(RegMemImm::imm(0)),
opcode: CmpOpcode::Cmp,
});
self.emit(Inst::TrapIf {
cc: CC::Z,
trap_code: TrapCode::IntegerDivisionByZero,
});
self.emit(Inst::SignExtendData {
size: size.into(),
src: dst.0.into(),
dst: dst.1.into(),
});
TrapCode::IntegerOverflow
}
DivKind::Unsigned => {
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::Xor,
src1: dst.1.into(),
src2: dst.1.into(),
dst: dst.1.into(),
});
TrapCode::IntegerDivisionByZero
}
};
self.emit(Inst::Div {
sign: kind.into(),
size: size.into(),
trap,
divisor: GprMem::unwrap_new(RegMem::reg(divisor.into())),
dividend_lo: dst.0.into(),
dividend_hi: dst.1.into(),
dst_quotient: dst.0.into(),
dst_remainder: dst.1.into(),
});
}
pub fn rem(&mut self, divisor: Reg, dst: (Reg, Reg), kind: RemKind, size: OperandSize) {
match kind {
RemKind::Signed => {
self.emit(Inst::SignExtendData {
size: size.into(),
src: dst.0.into(),
dst: dst.1.into(),
});
self.emit(Inst::CheckedSRemSeq {
size: size.into(),
divisor: divisor.into(),
dividend_lo: dst.0.into(),
dividend_hi: dst.1.into(),
dst_quotient: dst.0.into(),
dst_remainder: dst.1.into(),
});
}
RemKind::Unsigned => {
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::Xor,
src1: dst.1.into(),
src2: dst.1.into(),
dst: dst.1.into(),
});
self.emit(Inst::Div {
sign: DivSignedness::Unsigned,
trap: TrapCode::IntegerDivisionByZero,
size: size.into(),
divisor: GprMem::unwrap_new(RegMem::reg(divisor.into())),
dividend_lo: dst.0.into(),
dividend_hi: dst.1.into(),
dst_quotient: dst.0.into(),
dst_remainder: dst.1.into(),
});
}
}
}
pub fn mul_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
self.emit(Inst::IMulImm {
size: size.into(),
src1: dst.into(),
src2: imm,
dst: dst.into(),
});
}
pub fn mul_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::IMul {
size: size.into(),
src1: dst.into(),
src2: src.into(),
dst: dst.into(),
});
}
pub fn add_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
let imm = RegMemImm::imm(imm as u32);
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::Add,
src1: dst.into(),
src2: GprMemImm::unwrap_new(imm),
dst: dst.into(),
});
}
pub fn add_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::AluRmiR {
size: size.into(),
op: AluRmiROpcode::Add,
src1: dst.into(),
src2: src.into(),
dst: dst.into(),
});
}
pub fn cmp_ir(&mut self, src1: Reg, imm: i32, size: OperandSize) {
let imm = RegMemImm::imm(imm as u32);
self.emit(Inst::CmpRmiR {
size: size.into(),
opcode: CmpOpcode::Cmp,
src1: src1.into(),
src2: GprMemImm::unwrap_new(imm),
});
}
pub fn cmp_rr(&mut self, src1: Reg, src2: Reg, size: OperandSize) {
self.emit(Inst::CmpRmiR {
size: size.into(),
opcode: CmpOpcode::Cmp,
src1: src1.into(),
src2: src2.into(),
});
}
pub fn ucomis(&mut self, src1: Reg, src2: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Ucomiss,
OperandSize::S64 => SseOpcode::Ucomisd,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::XmmCmpRmR {
op,
src1: src1.into(),
src2: Xmm::from(src2).into(),
});
}
pub fn popcnt(&mut self, src: Reg, size: OperandSize) {
assert!(
self.isa_flags.has_popcnt() && self.isa_flags.has_sse42(),
"Requires has_popcnt and has_sse42 flags"
);
self.emit(Inst::UnaryRmR {
size: size.into(),
op: args::UnaryRmROpcode::Popcnt,
src: src.into(),
dst: src.into(),
});
}
pub fn test_rr(&mut self, src1: Reg, src2: Reg, size: OperandSize) {
self.emit(Inst::CmpRmiR {
size: size.into(),
opcode: CmpOpcode::Test,
src1: src1.into(),
src2: src2.into(),
})
}
pub fn setcc(&mut self, kind: IntCmpKind, dst: Reg) {
self.setcc_impl(kind.into(), dst);
}
pub fn setp(&mut self, dst: Reg) {
self.setcc_impl(CC::P, dst);
}
pub fn setnp(&mut self, dst: Reg) {
self.setcc_impl(CC::NP, dst);
}
fn setcc_impl(&mut self, cc: CC, dst: Reg) {
self.emit(Inst::Imm {
dst_size: args::OperandSize::Size32, simm64: 0,
dst: dst.into(),
});
self.emit(Inst::Setcc {
cc,
dst: dst.into(),
});
}
pub fn lzcnt(&mut self, src: Reg, dst: Reg, size: OperandSize) {
assert!(self.isa_flags.has_lzcnt(), "Requires has_lzcnt flag");
self.emit(Inst::UnaryRmR {
size: size.into(),
op: args::UnaryRmROpcode::Lzcnt,
src: src.into(),
dst: dst.into(),
});
}
pub fn tzcnt(&mut self, src: Reg, dst: Reg, size: OperandSize) {
assert!(self.isa_flags.has_bmi1(), "Requires has_bmi1 flag");
self.emit(Inst::UnaryRmR {
size: size.into(),
op: args::UnaryRmROpcode::Tzcnt,
src: src.into(),
dst: dst.into(),
});
}
pub fn bsr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::UnaryRmR {
size: size.into(),
op: args::UnaryRmROpcode::Bsr,
src: src.into(),
dst: dst.into(),
});
}
pub fn neg(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::Neg {
size: size.into(),
src: src.into(),
dst: dst.into(),
});
}
pub fn bsf(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::UnaryRmR {
size: size.into(),
op: args::UnaryRmROpcode::Bsf,
src: src.into(),
dst: dst.into(),
});
}
pub fn xmm_add_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Addss,
OperandSize::S64 => SseOpcode::Addsd,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::XmmRmRUnaligned {
op,
src1: Xmm::from(dst).into(),
src2: Xmm::from(src).into(),
dst: dst.into(),
});
}
pub fn xmm_sub_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Subss,
OperandSize::S64 => SseOpcode::Subsd,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::XmmRmRUnaligned {
op,
src1: Xmm::from(dst).into(),
src2: Xmm::from(src).into(),
dst: dst.into(),
});
}
pub fn xmm_mul_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Mulss,
OperandSize::S64 => SseOpcode::Mulsd,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::XmmRmRUnaligned {
op,
src1: Xmm::from(dst).into(),
src2: Xmm::from(src).into(),
dst: dst.into(),
});
}
pub fn xmm_div_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Divss,
OperandSize::S64 => SseOpcode::Divsd,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::XmmRmRUnaligned {
op,
src1: Xmm::from(dst).into(),
src2: Xmm::from(src).into(),
dst: dst.into(),
});
}
pub fn xmm_min_seq(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::XmmMinMaxSeq {
size: size.into(),
is_min: true,
lhs: src.into(),
rhs: dst.into(),
dst: dst.into(),
});
}
pub fn xmm_max_seq(&mut self, src: Reg, dst: Reg, size: OperandSize) {
self.emit(Inst::XmmMinMaxSeq {
size: size.into(),
is_min: false,
lhs: src.into(),
rhs: dst.into(),
dst: dst.into(),
});
}
pub fn xmm_rounds_rr(&mut self, src: Reg, dst: Reg, mode: RoundingMode, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Roundss,
OperandSize::S64 => SseOpcode::Roundsd,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
let imm: u8 = match mode {
RoundingMode::Nearest => 0x00,
RoundingMode::Down => 0x01,
RoundingMode::Up => 0x02,
RoundingMode::Zero => 0x03,
};
self.emit(Inst::XmmUnaryRmRImm {
op,
src: XmmMemAligned::from(Xmm::from(src)),
imm,
dst: dst.into(),
})
}
pub fn sqrt(&mut self, src: Reg, dst: Reg, size: OperandSize) {
let op = match size {
OperandSize::S32 => SseOpcode::Sqrtss,
OperandSize::S64 => SseOpcode::Sqrtsd,
OperandSize::S8 | OperandSize::S16 | OperandSize::S128 => unreachable!(),
};
self.emit(Inst::XmmRmR {
op,
src2: Xmm::from(src).into(),
src1: dst.into(),
dst: dst.into(),
})
}
pub fn call_with_reg(&mut self, callee: Reg) {
self.emit(Inst::CallUnknown {
dest: RegMem::reg(callee.into()),
info: None,
});
}
pub fn call_with_name(&mut self, name: UserExternalNameRef) {
let dest = ExternalName::user(name);
self.emit(Inst::CallKnown { dest, info: None });
}
pub fn call_with_lib(&mut self, lib: LibCall, dst: Reg) {
let dest = ExternalName::LibCall(lib);
self.emit(Inst::LoadExtName {
dst: Writable::from_reg(dst.into()),
name: Box::new(dest),
offset: 0,
distance: RelocDistance::Far,
});
self.call_with_reg(dst);
}
pub fn jmp_if(&mut self, cc: impl Into<CC>, taken: MachLabel) {
self.emit(Inst::JmpIf {
cc: cc.into(),
taken,
});
}
pub fn jmp(&mut self, target: MachLabel) {
self.emit(Inst::JmpKnown { dst: target });
}
pub fn jmp_table(
&mut self,
targets: SmallVec<[MachLabel; 4]>,
default: MachLabel,
index: Reg,
tmp1: Reg,
tmp2: Reg,
) {
self.emit(Inst::JmpTableSeq {
idx: index.into(),
tmp1: Writable::from_reg(tmp1.into()),
tmp2: Writable::from_reg(tmp2.into()),
default_target: default,
targets: Box::new(targets.to_vec()),
})
}
pub fn trap(&mut self, code: TrapCode) {
self.emit(Inst::Ud2 { trap_code: code })
}
pub fn trapif(&mut self, cc: impl Into<CC>, trap_code: TrapCode) {
self.emit(Inst::TrapIf {
cc: cc.into(),
trap_code,
});
}
pub fn lea(&mut self, addr: &Address, dst: Reg, size: OperandSize) {
let addr = Self::to_synthetic_amode(
addr,
&mut self.pool,
&mut self.constants,
&mut self.buffer,
MemFlags::trusted(),
);
self.emit(Inst::LoadEffectiveAddress {
addr,
dst: dst.into(),
size: size.into(),
});
}
}
pub(crate) struct PatchableAddToReg {
region: PatchRegion,
constant_offset: usize,
}
impl PatchableAddToReg {
pub(crate) fn new(reg: Reg, size: OperandSize, buf: &mut MachBuffer<Inst>) -> Self {
let open = buf.start_patchable();
let start = buf.cur_offset();
Self::add_inst_bytes(reg, size, buf);
let constant_offset = usize::try_from(buf.cur_offset() - start).unwrap();
buf.put4(0);
let region = buf.end_patchable(open);
Self {
region,
constant_offset,
}
}
fn add_inst_bytes(reg: Reg, size: OperandSize, buf: &mut MachBuffer<Inst>) {
match size {
OperandSize::S32 | OperandSize::S64 => {}
_ => {
panic!(
"{}-bit addition is not supported, please see the comment on PatchableAddToReg::new",
size.num_bits(),
)
}
}
let enc_g = 0;
debug_assert!(reg.is_int());
let enc_e = u8::try_from(reg.hw_enc()).unwrap();
RexFlags::from(args::OperandSize::from(size)).emit_two_op(buf, enc_g, enc_e);
buf.put1(0x81);
buf.put1(encode_modrm(0b11, enc_g & 7, enc_e & 7));
}
pub(crate) fn finalize(self, val: i32, buffer: &mut MachBuffer<Inst>) {
let slice = self.region.patch(buffer);
debug_assert_eq!(slice.len(), self.constant_offset + 4);
slice[self.constant_offset..].copy_from_slice(val.to_le_bytes().as_slice());
}
}