use super::ByteSink;
use crate::isa::x64::inst::args::Amode;
use crate::isa::x64::inst::{Inst, LabelUse, regs};
use crate::machinst::{MachBuffer, Reg, RegClass};
pub(crate) fn low8_will_sign_extend_to_32(x: u32) -> bool {
let xs = x as i32;
xs == ((xs << 24) >> 24)
}
#[inline(always)]
pub fn encode_modrm(m0d: u8, enc_reg_g: u8, rm_e: u8) -> u8 {
debug_assert!(m0d < 4);
debug_assert!(enc_reg_g < 8);
debug_assert!(rm_e < 8);
((m0d & 3) << 6) | ((enc_reg_g & 7) << 3) | (rm_e & 7)
}
#[inline(always)]
pub(crate) fn encode_sib(shift: u8, enc_index: u8, enc_base: u8) -> u8 {
debug_assert!(shift < 4);
debug_assert!(enc_index < 8);
debug_assert!(enc_base < 8);
((shift & 3) << 6) | ((enc_index & 7) << 3) | (enc_base & 7)
}
#[inline(always)]
pub(crate) fn int_reg_enc(reg: impl Into<Reg>) -> u8 {
let reg = reg.into();
debug_assert!(reg.is_real(), "reg = {reg:?}");
debug_assert_eq!(reg.class(), RegClass::Int);
reg.to_real_reg().unwrap().hw_enc()
}
#[allow(missing_docs)]
#[derive(PartialEq)]
pub enum OpcodeMap {
None,
_0F,
_0F38,
_0F3A,
}
impl OpcodeMap {
pub(crate) fn bits(&self) -> u8 {
match self {
OpcodeMap::None => 0b00,
OpcodeMap::_0F => 0b01,
OpcodeMap::_0F38 => 0b10,
OpcodeMap::_0F3A => 0b11,
}
}
}
impl Default for OpcodeMap {
fn default() -> Self {
Self::None
}
}
#[derive(PartialEq)]
pub enum LegacyPrefixes {
None,
_66,
_F0,
_66F0,
_F2,
_F3,
_66F3,
}
impl LegacyPrefixes {
#[inline(always)]
pub(crate) fn bits(&self) -> u8 {
match self {
Self::None => 0b00,
Self::_66 => 0b01,
Self::_F3 => 0b10,
Self::_F2 => 0b11,
_ => panic!(
"VEX and EVEX bits can only be extracted from single prefixes: None, 66, F3, F2"
),
}
}
}
impl Default for LegacyPrefixes {
fn default() -> Self {
Self::None
}
}
pub(crate) fn emit_modrm_sib_disp(
sink: &mut MachBuffer<Inst>,
enc_g: u8,
mem_e: &Amode,
bytes_at_end: u8,
evex_scaling: Option<i8>,
) {
match *mem_e {
Amode::ImmReg { simm32, base, .. } => {
let enc_e = int_reg_enc(base);
let mut imm = Imm::new(simm32, evex_scaling);
let enc_e_low3 = enc_e & 7;
if enc_e_low3 != regs::ENC_RSP {
if enc_e_low3 == regs::ENC_RBP {
imm.force_immediate();
}
sink.put1(encode_modrm(imm.m0d(), enc_g & 7, enc_e & 7));
imm.emit(sink);
} else {
sink.put1(encode_modrm(imm.m0d(), enc_g & 7, 0b100));
sink.put1(0b00_100_100);
imm.emit(sink);
}
}
Amode::ImmRegRegShift {
simm32,
base: reg_base,
index: reg_index,
shift,
..
} => {
let enc_base = int_reg_enc(*reg_base);
let enc_index = int_reg_enc(*reg_index);
assert!(enc_index != regs::ENC_RSP);
let mut imm = Imm::new(simm32, evex_scaling);
if enc_base & 7 == regs::ENC_RBP {
imm.force_immediate();
}
sink.put1(encode_modrm(imm.m0d(), enc_g & 7, 0b100));
sink.put1(encode_sib(shift, enc_index & 7, enc_base & 7));
imm.emit(sink);
}
Amode::RipRelative { ref target } => {
sink.put1(encode_modrm(0b00, enc_g & 7, 0b101));
let offset = sink.cur_offset();
sink.use_label_at_offset(offset, *target, LabelUse::JmpRel32);
sink.put4(-(i32::from(bytes_at_end)) as u32);
}
}
}
#[derive(Copy, Clone)]
enum Imm {
None,
Imm8(i8),
Imm32(i32),
}
impl Imm {
fn new(val: i32, evex_scaling: Option<i8>) -> Imm {
if val == 0 {
return Imm::None;
}
match evex_scaling {
Some(scaling) => {
if val % i32::from(scaling) == 0 {
let scaled = val / i32::from(scaling);
if low8_will_sign_extend_to_32(scaled as u32) {
return Imm::Imm8(scaled as i8);
}
}
Imm::Imm32(val)
}
None => match i8::try_from(val) {
Ok(val) => Imm::Imm8(val),
Err(_) => Imm::Imm32(val),
},
}
}
fn force_immediate(&mut self) {
if let Imm::None = self {
*self = Imm::Imm8(0);
}
}
fn m0d(&self) -> u8 {
match self {
Imm::None => 0b00,
Imm::Imm8(_) => 0b01,
Imm::Imm32(_) => 0b10,
}
}
fn emit<BS: ByteSink + ?Sized>(&self, sink: &mut BS) {
match self {
Imm::None => {}
Imm::Imm8(n) => sink.put1(*n as u8),
Imm::Imm32(n) => sink.put4(*n as u32),
}
}
}