use crate::ir::KnownSymbol;
use crate::ir::immediates::{Ieee32, Ieee64};
use crate::isa::x64::external::{AsmInst, CraneliftRegisters, PairedGpr};
use crate::isa::x64::inst::args::*;
use crate::isa::x64::inst::*;
use crate::isa::x64::lower::isle::generated_code::{Atomic128RmwSeqOp, AtomicRmwSeqOp};
use cranelift_assembler_x64 as asm;
fn emit_signed_cvt(
sink: &mut MachBuffer<Inst>,
info: &EmitInfo,
state: &mut EmitState,
src: Reg,
dst: Writable<Reg>,
to_f64: bool,
) {
assert!(src.is_real());
assert!(dst.to_reg().is_real());
let dst = WritableXmm::from_writable_reg(dst).unwrap();
if to_f64 {
asm::inst::cvtsi2sdq_a::new(dst, src).emit(sink, info, state);
} else {
asm::inst::cvtsi2ssq_a::new(dst, src).emit(sink, info, state);
}
}
fn one_way_jmp(sink: &mut MachBuffer<Inst>, cc: CC, label: MachLabel) {
let cond_start = sink.cur_offset();
let cond_disp_off = cond_start + 2;
sink.use_label_at_offset(cond_disp_off, label, LabelUse::JmpRel32);
emit_jcc_no_offset(sink, cc);
debug_assert_eq!(sink.cur_offset(), cond_disp_off + 4);
}
fn cond_jmp(sink: &mut MachBuffer<Inst>, cc: CC, label: MachLabel) {
let cond_start = sink.cur_offset();
let cond_disp_off = cond_start + 2;
let cond_end = cond_start + 6;
sink.use_label_at_offset(cond_disp_off, label, LabelUse::JmpRel32);
let inverted: [u8; 6] = [0x0F, 0x80 + (cc.invert().get_enc()), 0x00, 0x00, 0x00, 0x00];
sink.add_cond_branch(cond_start, cond_end, label, &inverted[..]);
emit_jcc_no_offset(sink, cc);
debug_assert_eq!(sink.cur_offset(), cond_disp_off + 4);
debug_assert_eq!(sink.cur_offset(), cond_end);
}
fn emit_jcc_no_offset(sink: &mut MachBuffer<Inst>, cc: CC) {
let inst: AsmInst = match cc {
CC::Z => asm::inst::je_d32::new(0).into(), CC::NZ => asm::inst::jne_d32::new(0).into(), CC::B => asm::inst::jb_d32::new(0).into(),
CC::NB => asm::inst::jae_d32::new(0).into(), CC::BE => asm::inst::jbe_d32::new(0).into(),
CC::NBE => asm::inst::ja_d32::new(0).into(), CC::L => asm::inst::jl_d32::new(0).into(),
CC::LE => asm::inst::jle_d32::new(0).into(),
CC::NL => asm::inst::jge_d32::new(0).into(), CC::NLE => asm::inst::jg_d32::new(0).into(), CC::O => asm::inst::jo_d32::new(0).into(),
CC::NO => asm::inst::jno_d32::new(0).into(),
CC::P => asm::inst::jp_d32::new(0).into(),
CC::NP => asm::inst::jnp_d32::new(0).into(),
CC::S => asm::inst::js_d32::new(0).into(),
CC::NS => asm::inst::jns_d32::new(0).into(),
};
inst.encode(&mut external::AsmCodeSink {
sink,
incoming_arg_offset: 0,
slot_offset: 0,
});
}
fn uncond_jmp(sink: &mut MachBuffer<Inst>, label: MachLabel) {
let uncond_start = sink.cur_offset();
let uncond_disp_off = uncond_start + 1;
let uncond_end = uncond_start + 5;
sink.use_label_at_offset(uncond_disp_off, label, LabelUse::JmpRel32);
sink.add_uncond_branch(uncond_start, uncond_end, label);
asm::inst::jmp_d32::new(0).encode(&mut external::AsmCodeSink {
sink,
incoming_arg_offset: 0,
slot_offset: 0,
});
debug_assert_eq!(sink.cur_offset(), uncond_disp_off + 4);
debug_assert_eq!(sink.cur_offset(), uncond_end);
}
fn emit_reloc(sink: &mut MachBuffer<Inst>, kind: Reloc, name: &ExternalName, addend: Addend) {
sink.add_reloc(kind, name, addend);
}
pub(crate) fn emit(
inst: &Inst,
sink: &mut MachBuffer<Inst>,
info: &EmitInfo,
state: &mut EmitState,
) {
if !inst.is_available(&info) {
let features = if let Inst::External { inst } = inst {
inst.features().to_string()
} else {
"see `is_available` source for feature term".to_string()
};
panic!(
"Cannot emit inst '{inst:?}' for target; failed to match ISA requirements: {features}"
);
}
match inst {
Inst::CheckedSRemSeq { divisor, .. } | Inst::CheckedSRemSeq8 { divisor, .. } => {
let (dst, size) = match inst {
Inst::CheckedSRemSeq {
dividend_lo,
dividend_hi,
dst_quotient,
dst_remainder,
size,
..
} => {
let dividend_lo = dividend_lo.to_reg();
let dividend_hi = dividend_hi.to_reg();
let dst_quotient = dst_quotient.to_reg().to_reg();
let dst_remainder = dst_remainder.to_reg().to_reg();
debug_assert_eq!(dividend_lo, regs::rax());
debug_assert_eq!(dividend_hi, regs::rdx());
debug_assert_eq!(dst_quotient, regs::rax());
debug_assert_eq!(dst_remainder, regs::rdx());
(regs::rdx(), *size)
}
Inst::CheckedSRemSeq8 { dividend, dst, .. } => {
let dividend = dividend.to_reg();
let dst = dst.to_reg().to_reg();
debug_assert_eq!(dividend, regs::rax());
debug_assert_eq!(dst, regs::rax());
(regs::rax(), OperandSize::Size8)
}
_ => unreachable!(),
};
let do_op = sink.get_label();
let done_label = sink.get_label();
let inst = Inst::cmp_mi_sxb(size, *divisor, -1);
inst.emit(sink, info, state);
one_way_jmp(sink, CC::NZ, do_op);
let inst = Inst::imm(OperandSize::Size64, 0, Writable::from_reg(dst));
inst.emit(sink, info, state);
let inst = Inst::jmp_known(done_label);
inst.emit(sink, info, state);
sink.bind_label(do_op, state.ctrl_plane_mut());
let rax = Gpr::RAX;
let rdx = Gpr::RDX;
let writable_rax = Writable::from_reg(rax);
let writable_rdx = Writable::from_reg(rdx);
let inst: AsmInst = match size {
OperandSize::Size8 => asm::inst::idivb_m::new(
PairedGpr::from(writable_rax),
*divisor,
TrapCode::INTEGER_DIVISION_BY_ZERO,
)
.into(),
OperandSize::Size16 => asm::inst::idivw_m::new(
PairedGpr::from(writable_rax),
PairedGpr::from(writable_rdx),
*divisor,
TrapCode::INTEGER_DIVISION_BY_ZERO,
)
.into(),
OperandSize::Size32 => asm::inst::idivl_m::new(
PairedGpr::from(writable_rax),
PairedGpr::from(writable_rdx),
*divisor,
TrapCode::INTEGER_DIVISION_BY_ZERO,
)
.into(),
OperandSize::Size64 => asm::inst::idivq_m::new(
PairedGpr::from(writable_rax),
PairedGpr::from(writable_rdx),
*divisor,
TrapCode::INTEGER_DIVISION_BY_ZERO,
)
.into(),
};
inst.emit(sink, info, state);
sink.bind_label(done_label, state.ctrl_plane_mut());
}
Inst::MovFromPReg { src, dst } => {
let src: Reg = (*src).into();
debug_assert!([regs::rsp(), regs::rbp(), regs::pinned_reg()].contains(&src));
asm::inst::movq_mr::new(*dst, Gpr::unwrap_new(src)).emit(sink, info, state);
}
Inst::MovToPReg { src, dst } => {
let dst: Reg = (*dst).into();
debug_assert!([regs::rsp(), regs::rbp(), regs::pinned_reg()].contains(&dst));
let dst = WritableGpr::from_writable_reg(Writable::from_reg(dst)).unwrap();
asm::inst::movq_mr::new(dst, *src).emit(sink, info, state);
}
Inst::XmmCmove {
ty,
cc,
consequent,
alternative,
dst,
} => {
let alternative = *alternative;
let dst = *dst;
debug_assert_eq!(alternative, dst.to_reg());
let consequent = *consequent;
let next = sink.get_label();
one_way_jmp(sink, cc.invert(), next);
Inst::gen_move(dst.map(|r| r.to_reg()), consequent.to_reg(), *ty)
.emit(sink, info, state);
sink.bind_label(next, state.ctrl_plane_mut());
}
Inst::StackProbeLoop {
tmp,
frame_size,
guard_size,
} => {
assert!(info.flags.enable_probestack());
assert!(guard_size.is_power_of_two());
let tmp = *tmp;
let probe_count = align_to(*frame_size, *guard_size) / guard_size;
let inst = Inst::gen_move(tmp, regs::rsp(), types::I64);
inst.emit(sink, info, state);
let guard_plus_count = i32::try_from(guard_size * probe_count)
.expect("`guard_size * probe_count` is too large to fit in a 32-bit immediate");
Inst::subq_mi(tmp, guard_plus_count).emit(sink, info, state);
let loop_start = sink.get_label();
sink.bind_label(loop_start, state.ctrl_plane_mut());
let rsp = Writable::from_reg(regs::rsp());
let guard_size_ = i32::try_from(*guard_size)
.expect("`guard_size` is too large to fit in a 32-bit immediate");
Inst::subq_mi(rsp, guard_size_).emit(sink, info, state);
asm::inst::movl_mi::new(Amode::imm_reg(0, regs::rsp()), 0i32.cast_unsigned())
.emit(sink, info, state);
let tmp = Gpr::unwrap_new(tmp.to_reg());
asm::inst::cmpq_rm::new(tmp, Gpr::RSP).emit(sink, info, state);
one_way_jmp(sink, CC::NZ, loop_start);
Inst::addq_mi(rsp, guard_plus_count).emit(sink, info, state);
}
Inst::CallKnown { info: call_info } => {
let start = sink.cur_offset();
let stack_map = state.take_stack_map();
asm::inst::callq_d::new(0).emit(sink, info, state);
let len = sink.cur_offset();
sink.add_reloc_at_offset(len - 4, Reloc::X86CallPCRel4, &call_info.dest, -4);
if let Some(s) = stack_map {
sink.push_user_stack_map(state, len, s);
}
if let Some(try_call) = call_info.try_call_info.as_ref() {
sink.add_try_call_site(
Some(state.frame_layout().sp_to_fp()),
try_call.exception_handlers(&state.frame_layout()),
);
} else {
sink.add_call_site();
}
if call_info.callee_pop_size > 0 {
let rsp = Writable::from_reg(regs::rsp());
let callee_pop_size = i32::try_from(call_info.callee_pop_size)
.expect("`callee_pop_size` is too large to fit in a 32-bit immediate");
Inst::subq_mi(rsp, callee_pop_size).emit(sink, info, state);
}
if call_info.patchable {
sink.add_patchable_call_site(sink.cur_offset() - start);
} else {
call_info.emit_retval_loads::<X64ABIMachineSpec, _, _>(
state.frame_layout().stackslots_size,
|inst| inst.emit(sink, info, state),
|_space_needed| None,
);
}
if let Some(try_call) = call_info.try_call_info.as_ref() {
let jmp = Inst::JmpKnown {
dst: try_call.continuation,
};
jmp.emit(sink, info, state);
}
}
Inst::ReturnCallKnown { info: call_info } => {
emit_return_call_common_sequence(sink, info, state, &call_info);
asm::inst::jmp_d32::new(0).emit(sink, info, state);
let offset = sink.cur_offset();
sink.add_reloc_at_offset(offset - 4, Reloc::X86CallPCRel4, &call_info.dest, -4);
sink.add_call_site();
}
Inst::ReturnCallUnknown { info: call_info } => {
let callee = call_info.dest;
emit_return_call_common_sequence(sink, info, state, &call_info);
asm::inst::jmpq_m::new(callee).emit(sink, info, state);
sink.add_call_site();
}
Inst::CallUnknown {
info: call_info, ..
} => {
let stack_map = state.take_stack_map();
let dest = match call_info.dest.clone() {
RegMem::Reg { reg } => asm::GprMem::Gpr(Gpr::unwrap_new(reg)),
RegMem::Mem { addr } => asm::GprMem::Mem(addr.into()),
};
asm::inst::callq_m::new(dest).emit(sink, info, state);
if let Some(s) = stack_map {
let offset = sink.cur_offset();
sink.push_user_stack_map(state, offset, s);
}
if let Some(try_call) = call_info.try_call_info.as_ref() {
sink.add_try_call_site(
Some(state.frame_layout().sp_to_fp()),
try_call.exception_handlers(&state.frame_layout()),
);
} else {
sink.add_call_site();
}
if call_info.callee_pop_size > 0 {
let rsp = Writable::from_reg(regs::rsp());
let callee_pop_size = i32::try_from(call_info.callee_pop_size)
.expect("`callee_pop_size` is too large to fit in a 32-bit immediate");
Inst::subq_mi(rsp, callee_pop_size).emit(sink, info, state);
}
call_info.emit_retval_loads::<X64ABIMachineSpec, _, _>(
state.frame_layout().stackslots_size,
|inst| inst.emit(sink, info, state),
|_space_needed| None,
);
if let Some(try_call) = call_info.try_call_info.as_ref() {
let jmp = Inst::JmpKnown {
dst: try_call.continuation,
};
jmp.emit(sink, info, state);
}
}
Inst::Args { .. } => {}
Inst::Rets { .. } => {}
Inst::StackSwitchBasic {
store_context_ptr,
load_context_ptr,
in_payload0,
out_payload0,
} => {
let (tmp1, tmp2) = {
let all = crate::isa::x64::abi::ALL_CLOBBERS;
let used_regs = [
**load_context_ptr,
**store_context_ptr,
**in_payload0,
*out_payload0.to_reg(),
];
let mut tmps = all.into_iter().filter_map(|preg| {
let reg: Reg = preg.into();
if !used_regs.contains(®) {
WritableGpr::from_writable_reg(isle::WritableReg::from_reg(reg))
} else {
None
}
});
(tmps.next().unwrap(), tmps.next().unwrap())
};
let layout = stack_switch::control_context_layout();
let rsp_offset = layout.stack_pointer_offset as i32;
let pc_offset = layout.ip_offset as i32;
let rbp_offset = layout.frame_pointer_offset as i32;
let resume = sink.get_label();
let mut exchange = |offset, reg| {
let addr = SyntheticAmode::real(Amode::imm_reg(offset, **load_context_ptr));
asm::inst::movq_rm::new(tmp1, addr).emit(sink, info, state);
asm::inst::movq_mr::new(
Amode::imm_reg(offset, **store_context_ptr),
Gpr::new(reg).unwrap(),
)
.emit(sink, info, state);
let dst = Writable::from_reg(reg);
asm::inst::movq_mr::new(dst.map(Gpr::unwrap_new), tmp1.to_reg())
.emit(sink, info, state);
};
exchange(rsp_offset, regs::rsp());
exchange(rbp_offset, regs::rbp());
let addr = SyntheticAmode::real(Amode::imm_reg(pc_offset, **load_context_ptr));
asm::inst::movq_rm::new(tmp1, addr).emit(sink, info, state);
let amode = Amode::RipRelative { target: resume };
asm::inst::leaq_rm::new(tmp2, amode).emit(sink, info, state);
asm::inst::movq_mr::new(
Amode::imm_reg(pc_offset, **store_context_ptr),
tmp2.to_reg(),
)
.emit(sink, info, state);
asm::inst::jmpq_m::new(tmp1.to_reg()).emit(sink, info, state);
sink.bind_label(resume, state.ctrl_plane_mut());
}
Inst::JmpKnown { dst } => uncond_jmp(sink, *dst),
Inst::WinchJmpIf { cc, taken } => one_way_jmp(sink, *cc, *taken),
Inst::JmpCond {
cc,
taken,
not_taken,
} => {
cond_jmp(sink, *cc, *taken);
uncond_jmp(sink, *not_taken);
}
Inst::JmpCondOr {
cc1,
cc2,
taken,
not_taken,
} => {
cond_jmp(sink, *cc1, *taken);
cond_jmp(sink, *cc2, *taken);
uncond_jmp(sink, *not_taken);
}
&Inst::JmpTableSeq {
idx,
tmp1,
tmp2,
ref targets,
ref default_target,
..
} => {
let start_of_jumptable = sink.get_label();
asm::inst::leaq_rm::new(tmp1, Amode::rip_relative(start_of_jumptable))
.emit(sink, info, state);
let inst = Inst::movsx_rm_r(
ExtMode::LQ,
RegMem::mem(Amode::imm_reg_reg_shift(
0,
Gpr::unwrap_new(tmp1.to_reg()),
Gpr::unwrap_new(idx),
2,
)),
tmp2,
);
inst.emit(sink, info, state);
asm::inst::addq_rm::new(tmp1, tmp2).emit(sink, info, state);
asm::inst::jmpq_m::new(tmp1.to_reg()).emit(sink, info, state);
sink.bind_label(start_of_jumptable, state.ctrl_plane_mut());
let jt_off = sink.cur_offset();
for &target in targets.iter().chain(core::iter::once(default_target)) {
let word_off = sink.cur_offset();
let off_into_table = word_off - jt_off;
sink.use_label_at_offset(word_off, target, LabelUse::PCRel32);
sink.put4(off_into_table);
}
}
Inst::TrapIf { cc, trap_code } => {
let trap_label = sink.defer_trap(*trap_code);
one_way_jmp(sink, *cc, trap_label);
}
Inst::TrapIfAnd {
cc1,
cc2,
trap_code,
} => {
let trap_label = sink.defer_trap(*trap_code);
let else_label = sink.get_label();
one_way_jmp(sink, cc1.invert(), else_label);
one_way_jmp(sink, *cc2, trap_label);
sink.bind_label(else_label, state.ctrl_plane_mut());
}
Inst::TrapIfOr {
cc1,
cc2,
trap_code,
} => {
let trap_label = sink.defer_trap(*trap_code);
one_way_jmp(sink, *cc1, trap_label);
one_way_jmp(sink, *cc2, trap_label);
}
Inst::XmmMinMaxSeq {
size,
is_min,
lhs,
rhs,
dst,
} => {
let rhs = rhs.to_reg();
let lhs = lhs.to_reg();
let dst = dst.to_writable_reg();
debug_assert_eq!(rhs, dst.to_reg());
let done = sink.get_label();
let propagate_nan = sink.get_label();
let do_min_max = sink.get_label();
let (add_op, cmp_op, and_op, or_op, min_max_op) = match size {
OperandSize::Size32 => (
asm::inst::addss_a::new(dst, lhs).into(),
asm::inst::ucomiss_a::new(dst.to_reg(), lhs).into(),
asm::inst::andps_a::new(dst, lhs).into(),
asm::inst::orps_a::new(dst, lhs).into(),
if *is_min {
asm::inst::minss_a::new(dst, lhs).into()
} else {
asm::inst::maxss_a::new(dst, lhs).into()
},
),
OperandSize::Size64 => (
asm::inst::addsd_a::new(dst, lhs).into(),
asm::inst::ucomisd_a::new(dst.to_reg(), lhs).into(),
asm::inst::andpd_a::new(dst, lhs).into(),
asm::inst::orpd_a::new(dst, lhs).into(),
if *is_min {
asm::inst::minsd_a::new(dst, lhs).into()
} else {
asm::inst::maxsd_a::new(dst, lhs).into()
},
),
_ => unreachable!(),
};
let add_op: AsmInst = add_op;
let or_op: AsmInst = or_op;
let min_max_op: AsmInst = min_max_op;
let cmp_op: AsmInst = cmp_op;
cmp_op.emit(sink, info, state);
one_way_jmp(sink, CC::NZ, do_min_max);
one_way_jmp(sink, CC::P, propagate_nan);
let inst: AsmInst = if *is_min { or_op } else { and_op };
inst.emit(sink, info, state);
let inst = Inst::jmp_known(done);
inst.emit(sink, info, state);
sink.bind_label(propagate_nan, state.ctrl_plane_mut());
add_op.emit(sink, info, state);
one_way_jmp(sink, CC::P, done);
sink.bind_label(do_min_max, state.ctrl_plane_mut());
min_max_op.emit(sink, info, state);
sink.bind_label(done, state.ctrl_plane_mut());
}
Inst::XmmUninitializedValue { .. } | Inst::GprUninitializedValue { .. } => {
}
Inst::CvtUint64ToFloatSeq {
dst_size,
src,
dst,
tmp_gpr1,
tmp_gpr2,
} => {
let src = src.to_reg();
let dst = dst.to_writable_reg();
let tmp_gpr1 = tmp_gpr1.to_writable_reg();
let tmp_gpr2 = tmp_gpr2.to_writable_reg();
assert_ne!(src, tmp_gpr1.to_reg());
assert_ne!(src, tmp_gpr2.to_reg());
let handle_negative = sink.get_label();
let done = sink.get_label();
asm::inst::cmpq_mi_sxb::new(src, 0).emit(sink, info, state);
one_way_jmp(sink, CC::L, handle_negative);
emit_signed_cvt(
sink,
info,
state,
src,
dst,
*dst_size == OperandSize::Size64,
);
let inst = Inst::jmp_known(done);
inst.emit(sink, info, state);
sink.bind_label(handle_negative, state.ctrl_plane_mut());
let inst = Inst::gen_move(tmp_gpr1, src, types::I64);
inst.emit(sink, info, state);
asm::inst::shrq_mi::new(tmp_gpr1, 1).emit(sink, info, state);
let inst = Inst::gen_move(tmp_gpr2, src, types::I64);
inst.emit(sink, info, state);
asm::inst::andq_mi_sxb::new(tmp_gpr2, 1).emit(sink, info, state);
asm::inst::orq_rm::new(tmp_gpr2, tmp_gpr1).emit(sink, info, state);
emit_signed_cvt(
sink,
info,
state,
tmp_gpr2.to_reg(),
dst,
*dst_size == OperandSize::Size64,
);
let inst: AsmInst = match *dst_size {
OperandSize::Size64 => asm::inst::addsd_a::new(dst, dst.to_reg()).into(),
OperandSize::Size32 => asm::inst::addss_a::new(dst, dst.to_reg()).into(),
_ => unreachable!(),
};
inst.emit(sink, info, state);
sink.bind_label(done, state.ctrl_plane_mut());
}
Inst::CvtFloatToSintSeq {
src_size,
dst_size,
is_saturating,
src,
dst,
tmp_gpr,
tmp_xmm,
} => {
use OperandSize::*;
let src = src.to_reg();
let dst = dst.to_writable_reg();
let tmp_gpr = tmp_gpr.to_writable_reg();
let tmp_xmm = tmp_xmm.to_writable_reg();
let cmp_op: AsmInst = match src_size {
Size64 => asm::inst::ucomisd_a::new(src, src).into(),
Size32 => asm::inst::ucomiss_a::new(src, src).into(),
_ => unreachable!(),
};
let cvtt_op = |dst, src| Inst::External {
inst: match (*src_size, *dst_size) {
(Size32, Size32) => asm::inst::cvttss2si_a::new(dst, src).into(),
(Size32, Size64) => asm::inst::cvttss2si_aq::new(dst, src).into(),
(Size64, Size32) => asm::inst::cvttsd2si_a::new(dst, src).into(),
(Size64, Size64) => asm::inst::cvttsd2si_aq::new(dst, src).into(),
_ => unreachable!(),
},
};
let done = sink.get_label();
cvtt_op(dst, src).emit(sink, info, state);
let inst = Inst::cmp_mi_sxb(*dst_size, Gpr::unwrap_new(dst.to_reg()), 1);
inst.emit(sink, info, state);
one_way_jmp(sink, CC::NO, done);
cmp_op.emit(sink, info, state);
if *is_saturating {
let not_nan = sink.get_label();
one_way_jmp(sink, CC::NP, not_nan);
let inst: AsmInst = match *dst_size {
OperandSize::Size32 => asm::inst::xorl_rm::new(dst, dst).into(),
OperandSize::Size64 => asm::inst::xorq_rm::new(dst, dst).into(),
_ => unreachable!(),
};
inst.emit(sink, info, state);
let inst = Inst::jmp_known(done);
inst.emit(sink, info, state);
sink.bind_label(not_nan, state.ctrl_plane_mut());
asm::inst::xorpd_a::new(tmp_xmm, tmp_xmm.to_reg()).emit(sink, info, state);
let inst: AsmInst = match src_size {
Size64 => asm::inst::ucomisd_a::new(tmp_xmm.to_reg(), src).into(),
Size32 => asm::inst::ucomiss_a::new(tmp_xmm.to_reg(), src).into(),
_ => unreachable!(),
};
inst.emit(sink, info, state);
one_way_jmp(sink, CC::NB, done);
if *dst_size == OperandSize::Size64 {
let inst = Inst::imm(OperandSize::Size64, 0x7fffffffffffffff, dst);
inst.emit(sink, info, state);
} else {
let inst = Inst::imm(OperandSize::Size32, 0x7fffffff, dst);
inst.emit(sink, info, state);
}
} else {
let inst = Inst::trap_if(CC::P, TrapCode::BAD_CONVERSION_TO_INTEGER);
inst.emit(sink, info, state);
let mut no_overflow_cc = CC::NB; let output_bits = dst_size.to_bits();
match *src_size {
OperandSize::Size32 => {
let cst = (-Ieee32::pow2(output_bits - 1)).bits();
let inst = Inst::imm(OperandSize::Size32, cst as u64, tmp_gpr);
inst.emit(sink, info, state);
}
OperandSize::Size64 => {
let cst = if output_bits < 64 {
no_overflow_cc = CC::NBE; Ieee64::fcvt_to_sint_negative_overflow(output_bits)
} else {
-Ieee64::pow2(output_bits - 1)
};
let inst = Inst::imm(OperandSize::Size64, cst.bits(), tmp_gpr);
inst.emit(sink, info, state);
}
_ => unreachable!(),
}
let inst: AsmInst = {
let tmp_xmm: WritableXmm = tmp_xmm.map(|r| Xmm::new(r).unwrap());
match src_size {
Size32 => asm::inst::movd_a::new(tmp_xmm, tmp_gpr).into(),
Size64 => asm::inst::movq_a::new(tmp_xmm, tmp_gpr).into(),
_ => unreachable!(),
}
};
inst.emit(sink, info, state);
let inst: AsmInst = match src_size {
Size64 => asm::inst::ucomisd_a::new(src, tmp_xmm.to_reg()).into(),
Size32 => asm::inst::ucomiss_a::new(src, tmp_xmm.to_reg()).into(),
_ => unreachable!(),
};
inst.emit(sink, info, state);
let inst = Inst::trap_if(no_overflow_cc.invert(), TrapCode::INTEGER_OVERFLOW);
inst.emit(sink, info, state);
asm::inst::xorpd_a::new(tmp_xmm, tmp_xmm.to_reg()).emit(sink, info, state);
let inst: AsmInst = match src_size {
Size64 => asm::inst::ucomisd_a::new(tmp_xmm.to_reg(), src).into(),
Size32 => asm::inst::ucomiss_a::new(tmp_xmm.to_reg(), src).into(),
_ => unreachable!(),
};
inst.emit(sink, info, state);
let inst = Inst::trap_if(CC::B, TrapCode::INTEGER_OVERFLOW);
inst.emit(sink, info, state);
}
sink.bind_label(done, state.ctrl_plane_mut());
}
Inst::CvtFloatToUintSeq {
src_size,
dst_size,
is_saturating,
src,
dst,
tmp_gpr,
tmp_xmm,
tmp_xmm2,
} => {
use OperandSize::*;
let src = src.to_reg();
let dst = dst.to_writable_reg();
let tmp_gpr = tmp_gpr.to_writable_reg();
let tmp_xmm = tmp_xmm.to_writable_reg();
let tmp_xmm2 = tmp_xmm2.to_writable_reg();
assert_ne!(tmp_xmm.to_reg(), src, "tmp_xmm clobbers src!");
let xor_op = |dst, src| Inst::External {
inst: match *dst_size {
Size32 => asm::inst::xorl_rm::new(dst, src).into(),
Size64 => asm::inst::xorq_rm::new(dst, src).into(),
_ => unreachable!(),
},
};
let subs_op = |dst, src| Inst::External {
inst: match *src_size {
Size32 => asm::inst::subss_a::new(dst, src).into(),
Size64 => asm::inst::subsd_a::new(dst, src).into(),
_ => unreachable!(),
},
};
let cvtt_op = |dst, src| Inst::External {
inst: match (*src_size, *dst_size) {
(Size32, Size32) => asm::inst::cvttss2si_a::new(dst, src).into(),
(Size32, Size64) => asm::inst::cvttss2si_aq::new(dst, src).into(),
(Size64, Size32) => asm::inst::cvttsd2si_a::new(dst, src).into(),
(Size64, Size64) => asm::inst::cvttsd2si_aq::new(dst, src).into(),
_ => unreachable!(),
},
};
let done = sink.get_label();
let cst = match src_size {
OperandSize::Size32 => Ieee32::pow2(dst_size.to_bits() - 1).bits() as u64,
OperandSize::Size64 => Ieee64::pow2(dst_size.to_bits() - 1).bits(),
_ => unreachable!(),
};
let inst = Inst::imm(*src_size, cst, tmp_gpr);
inst.emit(sink, info, state);
let inst: AsmInst = {
let tmp_xmm: WritableXmm = tmp_xmm.map(|r| Xmm::new(r).unwrap());
match src_size {
Size32 => asm::inst::movd_a::new(tmp_xmm, tmp_gpr).into(),
Size64 => asm::inst::movq_a::new(tmp_xmm, tmp_gpr).into(),
_ => unreachable!(),
}
};
inst.emit(sink, info, state);
let inst: AsmInst = match src_size {
Size64 => asm::inst::ucomisd_a::new(src, tmp_xmm.to_reg()).into(),
Size32 => asm::inst::ucomiss_a::new(src, tmp_xmm.to_reg()).into(),
_ => unreachable!(),
};
inst.emit(sink, info, state);
let handle_large = sink.get_label();
one_way_jmp(sink, CC::NB, handle_large);
if *is_saturating {
let not_nan = sink.get_label();
one_way_jmp(sink, CC::NP, not_nan);
xor_op(dst, dst).emit(sink, info, state);
let inst = Inst::jmp_known(done);
inst.emit(sink, info, state);
sink.bind_label(not_nan, state.ctrl_plane_mut());
} else {
let inst = Inst::trap_if(CC::P, TrapCode::BAD_CONVERSION_TO_INTEGER);
inst.emit(sink, info, state);
}
cvtt_op(dst, src).emit(sink, info, state);
let inst = Inst::cmp_mi_sxb(*dst_size, Gpr::unwrap_new(dst.to_reg()), 0);
inst.emit(sink, info, state);
one_way_jmp(sink, CC::NL, done);
if *is_saturating {
let inst: AsmInst = match *dst_size {
OperandSize::Size32 => asm::inst::xorl_rm::new(dst, dst).into(),
OperandSize::Size64 => asm::inst::xorq_rm::new(dst, dst).into(),
_ => unreachable!(),
};
inst.emit(sink, info, state);
let inst = Inst::jmp_known(done);
inst.emit(sink, info, state);
} else {
asm::inst::ud2_zo::new(TrapCode::INTEGER_OVERFLOW).emit(sink, info, state);
}
sink.bind_label(handle_large, state.ctrl_plane_mut());
let inst = Inst::gen_move(tmp_xmm2, src, types::F64);
inst.emit(sink, info, state);
subs_op(tmp_xmm2, tmp_xmm.to_reg()).emit(sink, info, state);
cvtt_op(dst, tmp_xmm2.to_reg()).emit(sink, info, state);
let inst = Inst::cmp_mi_sxb(*dst_size, Gpr::unwrap_new(dst.to_reg()), 0);
inst.emit(sink, info, state);
if *is_saturating {
let next_is_large = sink.get_label();
one_way_jmp(sink, CC::NL, next_is_large);
let inst = Inst::imm(
OperandSize::Size64,
if *dst_size == OperandSize::Size64 {
u64::max_value()
} else {
u32::max_value() as u64
},
dst,
);
inst.emit(sink, info, state);
let inst = Inst::jmp_known(done);
inst.emit(sink, info, state);
sink.bind_label(next_is_large, state.ctrl_plane_mut());
} else {
let inst = Inst::trap_if(CC::L, TrapCode::INTEGER_OVERFLOW);
inst.emit(sink, info, state);
}
if *dst_size == OperandSize::Size64 {
let inst = Inst::imm(OperandSize::Size64, 1 << 63, tmp_gpr);
inst.emit(sink, info, state);
asm::inst::addq_rm::new(dst, tmp_gpr).emit(sink, info, state);
} else {
asm::inst::addl_mi::new(dst, asm::Imm32::new(1 << 31)).emit(sink, info, state);
}
sink.bind_label(done, state.ctrl_plane_mut());
}
Inst::LoadExtName {
dst,
name,
offset,
distance,
} => {
let name = &**name;
let riprel = asm::Amode::RipRelative {
target: asm::DeferredTarget::None,
};
if info.flags.is_pic() {
asm::inst::movq_rm::new(*dst, riprel).emit(sink, info, state);
let cur = sink.cur_offset();
sink.add_reloc_at_offset(cur - 4, Reloc::X86GOTPCRel4, name, -4);
let offset = i32::try_from(*offset).unwrap();
if offset != 0 {
asm::inst::addq_mi_sxl::new(PairedGpr::from(*dst), offset)
.emit(sink, info, state);
}
} else if distance == &RelocDistance::Near {
asm::inst::leaq_rm::new(*dst, riprel).emit(sink, info, state);
let cur = sink.cur_offset();
sink.add_reloc_at_offset(cur - 4, Reloc::X86CallPCRel4, name, *offset - 4);
} else {
asm::inst::movabsq_oi::new(*dst, 0).emit(sink, info, state);
let cur = sink.cur_offset();
sink.add_reloc_at_offset(cur - 8, Reloc::Abs8, name, *offset);
}
}
Inst::AtomicRmwSeq {
ty,
op,
mem,
operand,
temp,
dst_old,
} => {
let operand = *operand;
let temp = *temp;
let temp_r = temp.map(|r| *r);
let dst_old = *dst_old;
let dst_old_r = dst_old.map(|r| *r);
debug_assert_eq!(dst_old.to_reg(), regs::rax());
let mem = mem.finalize(state.frame_layout(), sink).clone();
let again_label = sink.get_label();
let i1 = Inst::load(*ty, mem.clone(), dst_old_r, ExtKind::ZeroExtend);
i1.emit(sink, info, state);
sink.bind_label(again_label, state.ctrl_plane_mut());
asm::inst::movq_mr::new(temp, dst_old.to_reg()).emit(sink, info, state);
use AtomicRmwSeqOp as RmwOp;
match op {
RmwOp::Nand => {
asm::inst::andq_rm::new(temp, operand).emit(sink, info, state);
asm::inst::notq_m::new(PairedGpr::from(temp)).emit(sink, info, state);
}
RmwOp::Umin | RmwOp::Umax | RmwOp::Smin | RmwOp::Smax => {
let temp = temp.to_reg();
match *ty {
types::I8 => asm::inst::cmpb_mr::new(operand, temp).emit(sink, info, state),
types::I16 => {
asm::inst::cmpw_mr::new(operand, temp).emit(sink, info, state)
}
types::I32 => {
asm::inst::cmpl_mr::new(operand, temp).emit(sink, info, state)
}
types::I64 => {
asm::inst::cmpq_mr::new(operand, temp).emit(sink, info, state)
}
_ => unreachable!(),
}
match op {
RmwOp::Umin => {
asm::inst::cmovbeq_rm::new(temp_r, *operand).emit(sink, info, state)
}
RmwOp::Umax => {
asm::inst::cmovaeq_rm::new(temp_r, *operand).emit(sink, info, state)
}
RmwOp::Smin => {
asm::inst::cmovleq_rm::new(temp_r, *operand).emit(sink, info, state)
}
RmwOp::Smax => {
asm::inst::cmovgeq_rm::new(temp_r, *operand).emit(sink, info, state)
}
_ => unreachable!(),
}
}
RmwOp::And => {
asm::inst::andq_rm::new(temp, operand).emit(sink, info, state);
}
RmwOp::Or => {
asm::inst::orq_rm::new(temp, operand).emit(sink, info, state);
}
RmwOp::Xor => {
asm::inst::xorq_rm::new(temp, operand).emit(sink, info, state);
}
}
let temp = temp.to_reg();
let dst_old = PairedGpr::from(dst_old);
let inst: AsmInst = match *ty {
types::I8 => asm::inst::lock_cmpxchgb_mr::new(mem, temp, dst_old).into(),
types::I16 => asm::inst::lock_cmpxchgw_mr::new(mem, temp, dst_old).into(),
types::I32 => asm::inst::lock_cmpxchgl_mr::new(mem, temp, dst_old).into(),
types::I64 => asm::inst::lock_cmpxchgq_mr::new(mem, temp, dst_old).into(),
_ => unreachable!(),
};
inst.emit(sink, info, state);
one_way_jmp(sink, CC::NZ, again_label);
}
Inst::Atomic128RmwSeq {
op,
mem,
operand_low,
operand_high,
temp_low,
temp_high,
dst_old_low,
dst_old_high,
} => {
let operand_low = *operand_low;
let operand_high = *operand_high;
let temp_low = *temp_low;
let temp_high = *temp_high;
let dst_old_low = *dst_old_low;
let dst_old_high = *dst_old_high;
debug_assert_eq!(temp_low.to_reg(), regs::rbx());
debug_assert_eq!(temp_high.to_reg(), regs::rcx());
debug_assert_eq!(dst_old_low.to_reg(), regs::rax());
debug_assert_eq!(dst_old_high.to_reg(), regs::rdx());
let mem = mem.finalize(state.frame_layout(), sink).clone();
let again_label = sink.get_label();
asm::inst::movq_rm::new(dst_old_low, mem.clone()).emit(sink, info, state);
asm::inst::movq_rm::new(dst_old_high, mem.offset(8)).emit(sink, info, state);
sink.bind_label(again_label, state.ctrl_plane_mut());
asm::inst::movq_mr::new(temp_low, dst_old_low.to_reg()).emit(sink, info, state);
asm::inst::movq_mr::new(temp_high, dst_old_high.to_reg()).emit(sink, info, state);
use Atomic128RmwSeqOp as RmwOp;
match op {
RmwOp::Nand => {
asm::inst::andq_rm::new(temp_low, operand_low).emit(sink, info, state);
asm::inst::andq_rm::new(temp_high, operand_high).emit(sink, info, state);
asm::inst::notq_m::new(PairedGpr::from(temp_low)).emit(sink, info, state);
asm::inst::notq_m::new(PairedGpr::from(temp_high)).emit(sink, info, state);
}
RmwOp::Umin | RmwOp::Umax | RmwOp::Smin | RmwOp::Smax => {
asm::inst::cmpq_mr::new(temp_low.to_reg(), operand_low).emit(sink, info, state);
asm::inst::sbbq_rm::new(temp_high, operand_high).emit(sink, info, state);
asm::inst::movq_mr::new(temp_high, dst_old_high.to_reg())
.emit(sink, info, state);
match op {
RmwOp::Umin => {
asm::inst::cmovaeq_rm::new(temp_low, operand_low)
.emit(sink, info, state);
asm::inst::cmovaeq_rm::new(temp_high, operand_high)
.emit(sink, info, state);
}
RmwOp::Umax => {
asm::inst::cmovbq_rm::new(temp_low, operand_low)
.emit(sink, info, state);
asm::inst::cmovbq_rm::new(temp_high, operand_high)
.emit(sink, info, state);
}
RmwOp::Smin => {
asm::inst::cmovgeq_rm::new(temp_low, operand_low)
.emit(sink, info, state);
asm::inst::cmovgeq_rm::new(temp_high, operand_high)
.emit(sink, info, state);
}
RmwOp::Smax => {
asm::inst::cmovlq_rm::new(temp_low, operand_low)
.emit(sink, info, state);
asm::inst::cmovlq_rm::new(temp_high, operand_high)
.emit(sink, info, state);
}
_ => unreachable!(),
}
}
RmwOp::Add => {
asm::inst::addq_rm::new(temp_low, operand_low).emit(sink, info, state);
asm::inst::adcq_rm::new(temp_high, operand_high).emit(sink, info, state);
}
RmwOp::Sub => {
asm::inst::subq_rm::new(temp_low, operand_low).emit(sink, info, state);
asm::inst::sbbq_rm::new(temp_high, operand_high).emit(sink, info, state);
}
RmwOp::And => {
asm::inst::andq_rm::new(temp_low, operand_low).emit(sink, info, state);
asm::inst::andq_rm::new(temp_high, operand_high).emit(sink, info, state);
}
RmwOp::Or => {
asm::inst::orq_rm::new(temp_low, operand_low).emit(sink, info, state);
asm::inst::orq_rm::new(temp_high, operand_high).emit(sink, info, state);
}
RmwOp::Xor => {
asm::inst::xorq_rm::new(temp_low, operand_low).emit(sink, info, state);
asm::inst::xorq_rm::new(temp_high, operand_high).emit(sink, info, state);
}
}
asm::inst::lock_cmpxchg16b_m::new(
PairedGpr::from(dst_old_low),
PairedGpr::from(dst_old_high),
temp_low.to_reg(),
temp_high.to_reg(),
mem,
)
.emit(sink, info, state);
one_way_jmp(sink, CC::NZ, again_label);
}
Inst::Atomic128XchgSeq {
mem,
operand_low,
operand_high,
dst_old_low,
dst_old_high,
} => {
let operand_low = *operand_low;
let operand_high = *operand_high;
let dst_old_low = *dst_old_low;
let dst_old_high = *dst_old_high;
debug_assert_eq!(operand_low, regs::rbx());
debug_assert_eq!(operand_high, regs::rcx());
debug_assert_eq!(dst_old_low.to_reg(), regs::rax());
debug_assert_eq!(dst_old_high.to_reg(), regs::rdx());
let mem = mem.finalize(state.frame_layout(), sink).clone();
let again_label = sink.get_label();
asm::inst::movq_rm::new(dst_old_low, mem.clone()).emit(sink, info, state);
asm::inst::movq_rm::new(dst_old_high, mem.offset(8)).emit(sink, info, state);
sink.bind_label(again_label, state.ctrl_plane_mut());
asm::inst::lock_cmpxchg16b_m::new(
PairedGpr::from(dst_old_low),
PairedGpr::from(dst_old_high),
operand_low,
operand_high,
mem,
)
.emit(sink, info, state);
one_way_jmp(sink, CC::NZ, again_label);
}
Inst::ElfTlsGetAddr { symbol, dst } => {
let dst = dst.to_reg().to_reg();
debug_assert_eq!(dst, regs::rax());
sink.put1(0x66); sink.put1(0b01001000); sink.put1(0x8d); sink.put1(0x3d); emit_reloc(sink, Reloc::ElfX86_64TlsGd, symbol, -4);
sink.put4(0);
sink.put1(0x66); sink.put1(0x66); sink.put1(0b01001000); sink.put1(0xe8); emit_reloc(
sink,
Reloc::X86CallPLTRel4,
&ExternalName::LibCall(LibCall::ElfTlsGetAddr),
-4,
);
sink.put4(0); }
Inst::MachOTlsGetAddr { symbol, dst } => {
let dst = dst.to_reg().to_reg();
debug_assert_eq!(dst, regs::rax());
sink.put1(0x48); sink.put1(0x8b); sink.put1(0x3d); emit_reloc(sink, Reloc::MachOX86_64Tlv, symbol, -4);
sink.put4(0);
asm::inst::callq_m::new(asm::Amode::ImmReg {
base: Gpr::RDI,
simm32: asm::AmodeOffsetPlusKnownOffset::ZERO,
trap: None,
})
.emit(sink, info, state);
}
Inst::CoffTlsGetAddr { symbol, dst, tmp } => {
let dst = dst.to_reg().to_reg();
debug_assert_eq!(dst, regs::rax());
let tmp = tmp.to_reg().to_reg();
debug_assert_eq!(tmp, regs::rcx());
sink.put1(0x8b); sink.put1(0x05);
emit_reloc(
sink,
Reloc::X86PCRel4,
&ExternalName::KnownSymbol(KnownSymbol::CoffTlsIndex),
-4,
);
sink.put4(0);
sink.put_data(&[
0x65, 0x48, 0x8b, 0x0c, 0x25, 0x58, 0x00, 0x00, 0x00,
]);
sink.put_data(&[0x48, 0x8b, 0x04, 0xc1]);
sink.put1(0x48);
sink.put1(0x8d);
sink.put1(0x80);
emit_reloc(sink, Reloc::X86SecRel, symbol, 0);
sink.put4(0); }
Inst::Unwind { inst } => {
sink.add_unwind(inst.clone());
}
Inst::DummyUse { .. } => {
}
Inst::LabelAddress { dst, label } => {
asm::inst::leaq_rm::new(*dst, Amode::rip_relative(*label)).emit(sink, info, state);
}
Inst::SequencePoint { .. } => {
}
Inst::External { inst } => {
let frame = state.frame_layout();
emit_maybe_shrink(
inst,
&mut external::AsmCodeSink {
sink,
incoming_arg_offset: i32::try_from(
frame.tail_args_size + frame.setup_area_size,
)
.unwrap(),
slot_offset: i32::try_from(frame.outgoing_args_size).unwrap(),
},
);
}
}
state.clear_post_insn();
}
fn emit_return_call_common_sequence<T>(
sink: &mut MachBuffer<Inst>,
info: &EmitInfo,
state: &mut EmitState,
call_info: &ReturnCallInfo<T>,
) {
assert!(
info.flags.preserve_frame_pointers(),
"frame pointers aren't fundamentally required for tail calls, \
but the current implementation relies on them being present"
);
let tmp = call_info.tmp.to_writable_reg();
for inst in
X64ABIMachineSpec::gen_clobber_restore(CallConv::Tail, &info.flags, state.frame_layout())
{
inst.emit(sink, info, state);
}
for inst in X64ABIMachineSpec::gen_epilogue_frame_restore(
CallConv::Tail,
&info.flags,
&info.isa_flags,
state.frame_layout(),
) {
inst.emit(sink, info, state);
}
let incoming_args_diff = state.frame_layout().tail_args_size - call_info.new_stack_arg_size;
if incoming_args_diff > 0 {
let addr = Amode::imm_reg(0, regs::rsp());
asm::inst::movq_rm::new(tmp, addr).emit(sink, info, state);
asm::inst::movq_mr::new(
Amode::imm_reg(i32::try_from(incoming_args_diff).unwrap(), regs::rsp()),
Gpr::unwrap_new(tmp.to_reg()),
)
.emit(sink, info, state);
let rsp = Writable::from_reg(regs::rsp());
let incoming_args_diff = i32::try_from(incoming_args_diff)
.expect("`incoming_args_diff` is too large to fit in a 32-bit signed immediate");
Inst::addq_mi(rsp, incoming_args_diff).emit(sink, info, state);
}
}
trait ExternalEmit {
fn emit(self, sink: &mut MachBuffer<Inst>, info: &EmitInfo, state: &mut EmitState);
}
impl<I> ExternalEmit for I
where
I: Into<asm::inst::Inst<CraneliftRegisters>>,
{
fn emit(self, sink: &mut MachBuffer<Inst>, info: &EmitInfo, state: &mut EmitState) {
Inst::External { inst: self.into() }.emit(sink, info, state)
}
}
fn emit_maybe_shrink(inst: &AsmInst, sink: &mut impl asm::CodeSink) {
use cranelift_assembler_x64::GprMem;
use cranelift_assembler_x64::inst::*;
type R = CraneliftRegisters;
const RAX: PairedGpr = PairedGpr {
read: Gpr::RAX,
write: Writable::from_reg(Gpr::RAX),
};
const RAX_RM: GprMem<PairedGpr, Gpr> = GprMem::Gpr(RAX);
match *inst {
Inst::andb_mi(andb_mi { rm8: RAX_RM, imm8 }) => andb_i::<R>::new(RAX, imm8).encode(sink),
Inst::andw_mi(andw_mi {
rm16: RAX_RM,
imm16,
}) => andw_i::<R>::new(RAX, imm16).encode(sink),
Inst::andl_mi(andl_mi {
rm32: RAX_RM,
imm32,
}) => andl_i::<R>::new(RAX, imm32).encode(sink),
Inst::andq_mi_sxl(andq_mi_sxl {
rm64: RAX_RM,
imm32,
}) => andq_i_sxl::<R>::new(RAX, imm32).encode(sink),
Inst::orb_mi(orb_mi { rm8: RAX_RM, imm8 }) => orb_i::<R>::new(RAX, imm8).encode(sink),
Inst::orw_mi(orw_mi {
rm16: RAX_RM,
imm16,
}) => orw_i::<R>::new(RAX, imm16).encode(sink),
Inst::orl_mi(orl_mi {
rm32: RAX_RM,
imm32,
}) => orl_i::<R>::new(RAX, imm32).encode(sink),
Inst::orq_mi_sxl(orq_mi_sxl {
rm64: RAX_RM,
imm32,
}) => orq_i_sxl::<R>::new(RAX, imm32).encode(sink),
Inst::xorb_mi(xorb_mi { rm8: RAX_RM, imm8 }) => xorb_i::<R>::new(RAX, imm8).encode(sink),
Inst::xorw_mi(xorw_mi {
rm16: RAX_RM,
imm16,
}) => xorw_i::<R>::new(RAX, imm16).encode(sink),
Inst::xorl_mi(xorl_mi {
rm32: RAX_RM,
imm32,
}) => xorl_i::<R>::new(RAX, imm32).encode(sink),
Inst::xorq_mi_sxl(xorq_mi_sxl {
rm64: RAX_RM,
imm32,
}) => xorq_i_sxl::<R>::new(RAX, imm32).encode(sink),
Inst::addb_mi(addb_mi { rm8: RAX_RM, imm8 }) => addb_i::<R>::new(RAX, imm8).encode(sink),
Inst::addw_mi(addw_mi {
rm16: RAX_RM,
imm16,
}) => addw_i::<R>::new(RAX, imm16).encode(sink),
Inst::addl_mi(addl_mi {
rm32: RAX_RM,
imm32,
}) => addl_i::<R>::new(RAX, imm32).encode(sink),
Inst::addq_mi_sxl(addq_mi_sxl {
rm64: RAX_RM,
imm32,
}) => addq_i_sxl::<R>::new(RAX, imm32).encode(sink),
Inst::adcb_mi(adcb_mi { rm8: RAX_RM, imm8 }) => adcb_i::<R>::new(RAX, imm8).encode(sink),
Inst::adcw_mi(adcw_mi {
rm16: RAX_RM,
imm16,
}) => adcw_i::<R>::new(RAX, imm16).encode(sink),
Inst::adcl_mi(adcl_mi {
rm32: RAX_RM,
imm32,
}) => adcl_i::<R>::new(RAX, imm32).encode(sink),
Inst::adcq_mi_sxl(adcq_mi_sxl {
rm64: RAX_RM,
imm32,
}) => adcq_i_sxl::<R>::new(RAX, imm32).encode(sink),
Inst::subb_mi(subb_mi { rm8: RAX_RM, imm8 }) => subb_i::<R>::new(RAX, imm8).encode(sink),
Inst::subw_mi(subw_mi {
rm16: RAX_RM,
imm16,
}) => subw_i::<R>::new(RAX, imm16).encode(sink),
Inst::subl_mi(subl_mi {
rm32: RAX_RM,
imm32,
}) => subl_i::<R>::new(RAX, imm32).encode(sink),
Inst::subq_mi_sxl(subq_mi_sxl {
rm64: RAX_RM,
imm32,
}) => subq_i_sxl::<R>::new(RAX, imm32).encode(sink),
Inst::sbbb_mi(sbbb_mi { rm8: RAX_RM, imm8 }) => sbbb_i::<R>::new(RAX, imm8).encode(sink),
Inst::sbbw_mi(sbbw_mi {
rm16: RAX_RM,
imm16,
}) => sbbw_i::<R>::new(RAX, imm16).encode(sink),
Inst::sbbl_mi(sbbl_mi {
rm32: RAX_RM,
imm32,
}) => sbbl_i::<R>::new(RAX, imm32).encode(sink),
Inst::sbbq_mi_sxl(sbbq_mi_sxl {
rm64: RAX_RM,
imm32,
}) => sbbq_i_sxl::<R>::new(RAX, imm32).encode(sink),
Inst::cmpb_mi(cmpb_mi {
rm8: GprMem::Gpr(Gpr::RAX),
imm8,
}) => cmpb_i::<R>::new(Gpr::RAX, imm8).encode(sink),
Inst::cmpw_mi(cmpw_mi {
rm16: GprMem::Gpr(Gpr::RAX),
imm16,
}) => cmpw_i::<R>::new(Gpr::RAX, imm16).encode(sink),
Inst::cmpl_mi(cmpl_mi {
rm32: GprMem::Gpr(Gpr::RAX),
imm32,
}) => cmpl_i::<R>::new(Gpr::RAX, imm32).encode(sink),
Inst::cmpq_mi(cmpq_mi {
rm64: GprMem::Gpr(Gpr::RAX),
imm32,
}) => cmpq_i::<R>::new(Gpr::RAX, imm32).encode(sink),
Inst::testb_mi(testb_mi {
rm8: GprMem::Gpr(Gpr::RAX),
imm8,
}) => testb_i::<R>::new(Gpr::RAX, imm8).encode(sink),
Inst::testw_mi(testw_mi {
rm16: GprMem::Gpr(Gpr::RAX),
imm16,
}) => testw_i::<R>::new(Gpr::RAX, imm16).encode(sink),
Inst::testl_mi(testl_mi {
rm32: GprMem::Gpr(Gpr::RAX),
imm32,
}) => testl_i::<R>::new(Gpr::RAX, imm32).encode(sink),
Inst::testq_mi(testq_mi {
rm64: GprMem::Gpr(Gpr::RAX),
imm32,
}) => testq_i::<R>::new(Gpr::RAX, imm32).encode(sink),
Inst::leal_rm(leal_rm { r32, m32 }) => emit_lea(
r32,
m32,
sink,
|dst, amode, s| leal_rm::<R>::new(dst, amode).encode(s),
|dst, simm32, s| addl_mi::<R>::new(dst, simm32.cast_unsigned()).encode(s),
|dst, reg, s| addl_rm::<R>::new(dst, reg).encode(s),
),
Inst::leaq_rm(leaq_rm { r64, m64 }) => emit_lea(
r64,
m64,
sink,
|dst, amode, s| leaq_rm::<R>::new(dst, amode).encode(s),
|dst, simm32, s| addq_mi_sxl::<R>::new(dst, simm32).encode(s),
|dst, reg, s| addq_rm::<R>::new(dst, reg).encode(s),
),
_ => inst.encode(sink),
}
}
fn emit_lea<S>(
dst: asm::Gpr<WritableGpr>,
addr: asm::Amode<Gpr>,
sink: &mut S,
lea: fn(WritableGpr, asm::Amode<Gpr>, &mut S),
add_mi: fn(PairedGpr, i32, &mut S),
add_rm: fn(PairedGpr, Gpr, &mut S),
) where
S: asm::CodeSink,
{
match addr {
asm::Amode::ImmReg {
base,
simm32:
asm::AmodeOffsetPlusKnownOffset {
simm32,
offset: None,
},
trap: None,
} if dst.as_ref().to_reg() == base => add_mi(
PairedGpr {
read: base,
write: *dst.as_ref(),
},
simm32.value(),
sink,
),
asm::Amode::ImmRegRegShift {
base,
index,
scale: asm::Scale::One,
simm32: asm::AmodeOffset::ZERO,
trap: None,
} => {
if dst.as_ref().to_reg() == base {
add_rm(
PairedGpr {
read: base,
write: *dst.as_ref(),
},
*index.as_ref(),
sink,
)
} else if dst.as_ref().to_reg() == *index.as_ref() {
add_rm(
PairedGpr {
read: *index.as_ref(),
write: *dst.as_ref(),
},
base,
sink,
)
} else {
lea(*dst.as_ref(), addr, sink)
}
}
_ => lea(*dst.as_ref(), addr, sink),
}
}