use alloc::string::{String, ToString};
use crate::api::{AsReg, CodeSink, Constant, KnownOffset, Label, TrapCode};
use crate::gpr::{self, NonRspGpr, Size};
use crate::rex::{Disp, RexPrefix, encode_modrm, encode_sib};
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
pub enum Amode<R: AsReg> {
ImmReg {
base: R,
simm32: AmodeOffsetPlusKnownOffset,
trap: Option<TrapCode>,
},
ImmRegRegShift {
base: R,
index: NonRspGpr<R>,
scale: Scale,
simm32: AmodeOffset,
trap: Option<TrapCode>,
},
RipRelative {
target: DeferredTarget,
},
}
impl<R: AsReg> Amode<R> {
pub fn trap_code(&self) -> Option<TrapCode> {
match self {
Amode::ImmReg { trap, .. } | Amode::ImmRegRegShift { trap, .. } => *trap,
Amode::RipRelative { .. } => None,
}
}
#[must_use]
pub(crate) fn as_rex_prefix(&self, enc_reg: u8, has_w_bit: bool, uses_8bit: bool) -> RexPrefix {
match self {
Amode::ImmReg { base, .. } => {
RexPrefix::mem_op(enc_reg, base.enc(), has_w_bit, uses_8bit)
}
Amode::ImmRegRegShift { base, index, .. } => {
RexPrefix::three_op(enc_reg, index.enc(), base.enc(), has_w_bit, uses_8bit)
}
Amode::RipRelative { .. } => RexPrefix::two_op(enc_reg, 0, has_w_bit, uses_8bit),
}
}
pub(crate) fn encode_rex_suffixes(
&self,
sink: &mut impl CodeSink,
enc_reg: u8,
bytes_at_end: u8,
evex_scaling: Option<i8>,
) {
emit_modrm_sib_disp(sink, enc_reg, self, bytes_at_end, evex_scaling);
}
pub(crate) fn encode_bx_regs(&self) -> (Option<u8>, Option<u8>) {
match self {
Amode::ImmReg { base, .. } => (Some(base.enc()), None),
Amode::ImmRegRegShift { base, index, .. } => (Some(base.enc()), Some(index.enc())),
Amode::RipRelative { .. } => (None, None),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct AmodeOffset(i32);
impl AmodeOffset {
pub const ZERO: AmodeOffset = AmodeOffset::new(0);
#[must_use]
pub const fn new(value: i32) -> Self {
Self(value)
}
#[must_use]
pub fn value(self) -> i32 {
self.0
}
}
impl From<i32> for AmodeOffset {
fn from(value: i32) -> Self {
Self(value)
}
}
impl core::fmt::LowerHex for AmodeOffset {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if self.0 == 0 {
return Ok(());
}
if self.0 < 0 {
write!(f, "-")?;
}
if self.0 > 9 || self.0 < -9 {
write!(f, "0x")?;
}
let abs = match self.0.checked_abs() {
Some(i) => i,
None => -2_147_483_648,
};
core::fmt::LowerHex::fmt(&abs, f)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct AmodeOffsetPlusKnownOffset {
pub simm32: AmodeOffset,
pub offset: Option<KnownOffset>,
}
impl AmodeOffsetPlusKnownOffset {
pub const ZERO: AmodeOffsetPlusKnownOffset = AmodeOffsetPlusKnownOffset {
simm32: AmodeOffset::ZERO,
offset: None,
};
#[must_use]
pub fn value(&self, sink: &impl CodeSink) -> i32 {
let known_offset = match self.offset {
Some(offset) => sink.known_offset(offset),
None => 0,
};
known_offset
.checked_add(self.simm32.value())
.expect("no wrapping")
}
}
impl core::fmt::LowerHex for AmodeOffsetPlusKnownOffset {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if let Some(offset) = self.offset {
write!(f, "<offset:{offset}>+")?;
}
core::fmt::LowerHex::fmt(&self.simm32, f)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
pub enum DeferredTarget {
Label(Label),
Constant(Constant),
None,
}
impl<R: AsReg> core::fmt::Display for Amode<R> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let pointer_width = Size::Quadword;
match self {
Amode::ImmReg { simm32, base, .. } => {
let base = base.to_string(Some(pointer_width));
write!(f, "{simm32:x}({base})")
}
Amode::ImmRegRegShift {
simm32,
base,
index,
scale,
..
} => {
let base = base.to_string(Some(pointer_width));
let index = index.to_string(pointer_width);
let shift = scale.shift();
if shift > 1 {
write!(f, "{simm32:x}({base}, {index}, {shift})")
} else {
write!(f, "{simm32:x}({base}, {index})")
}
}
Amode::RipRelative { .. } => write!(f, "(%rip)"),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
pub enum Scale {
One,
Two,
Four,
Eight,
}
impl Scale {
#[must_use]
pub fn new(enc: u8) -> Self {
match enc {
0b00 => Scale::One,
0b01 => Scale::Two,
0b10 => Scale::Four,
0b11 => Scale::Eight,
_ => panic!("invalid scale encoding: {enc}"),
}
}
fn enc(&self) -> u8 {
match self {
Scale::One => 0b00,
Scale::Two => 0b01,
Scale::Four => 0b10,
Scale::Eight => 0b11,
}
}
fn shift(&self) -> u8 {
1 << self.enc()
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
#[allow(
clippy::module_name_repetitions,
reason = "'GprMem' indicates this has GPR and memory variants"
)]
pub enum GprMem<R: AsReg, M: AsReg> {
Gpr(R),
Mem(Amode<M>),
}
impl<R: AsReg, M: AsReg> GprMem<R, M> {
pub fn to_string(&self, size: Size) -> String {
match self {
GprMem::Gpr(gpr) => gpr.to_string(Some(size)),
GprMem::Mem(amode) => amode.to_string(),
}
}
#[must_use]
pub(crate) fn as_rex_prefix(&self, enc_reg: u8, has_w_bit: bool, uses_8bit: bool) -> RexPrefix {
match self {
GprMem::Gpr(rm) => RexPrefix::two_op(enc_reg, rm.enc(), has_w_bit, uses_8bit),
GprMem::Mem(amode) => amode.as_rex_prefix(enc_reg, has_w_bit, uses_8bit),
}
}
pub(crate) fn encode_rex_suffixes(
&self,
sink: &mut impl CodeSink,
enc_reg: u8,
bytes_at_end: u8,
evex_scaling: Option<i8>,
) {
match self {
GprMem::Gpr(gpr) => {
sink.put1(encode_modrm(0b11, enc_reg & 0b111, gpr.enc() & 0b111));
}
GprMem::Mem(amode) => {
amode.encode_rex_suffixes(sink, enc_reg, bytes_at_end, evex_scaling);
}
}
}
pub(crate) fn encode_bx_regs(&self) -> (Option<u8>, Option<u8>) {
match self {
GprMem::Gpr(reg) => (Some(reg.enc()), None),
GprMem::Mem(amode) => amode.encode_bx_regs(),
}
}
}
impl<R: AsReg, M: AsReg> From<R> for GprMem<R, M> {
fn from(reg: R) -> GprMem<R, M> {
GprMem::Gpr(reg)
}
}
impl<R: AsReg, M: AsReg> From<Amode<M>> for GprMem<R, M> {
fn from(amode: Amode<M>) -> GprMem<R, M> {
GprMem::Mem(amode)
}
}
#[derive(Copy, Clone, Debug)]
#[cfg_attr(any(test, feature = "fuzz"), derive(arbitrary::Arbitrary))]
#[allow(
clippy::module_name_repetitions,
reason = "'XmmMem' indicates this has Xmm and memory variants"
)]
pub enum XmmMem<R: AsReg, M: AsReg> {
Xmm(R),
Mem(Amode<M>),
}
impl<R: AsReg, M: AsReg> XmmMem<R, M> {
pub fn to_string(&self) -> String {
match self {
XmmMem::Xmm(xmm) => xmm.to_string(None),
XmmMem::Mem(amode) => amode.to_string(),
}
}
#[must_use]
pub(crate) fn as_rex_prefix(&self, enc_reg: u8, has_w_bit: bool, uses_8bit: bool) -> RexPrefix {
match self {
XmmMem::Xmm(rm) => RexPrefix::two_op(enc_reg, rm.enc(), has_w_bit, uses_8bit),
XmmMem::Mem(amode) => amode.as_rex_prefix(enc_reg, has_w_bit, uses_8bit),
}
}
pub(crate) fn encode_rex_suffixes(
&self,
sink: &mut impl CodeSink,
enc_reg: u8,
bytes_at_end: u8,
evex_scaling: Option<i8>,
) {
match self {
XmmMem::Xmm(xmm) => {
sink.put1(encode_modrm(0b11, enc_reg & 0b111, xmm.enc() & 0b111));
}
XmmMem::Mem(amode) => {
amode.encode_rex_suffixes(sink, enc_reg, bytes_at_end, evex_scaling);
}
}
}
pub(crate) fn encode_bx_regs(&self) -> (Option<u8>, Option<u8>) {
match self {
XmmMem::Xmm(reg) => (Some(reg.enc()), None),
XmmMem::Mem(amode) => amode.encode_bx_regs(),
}
}
}
impl<R: AsReg, M: AsReg> From<R> for XmmMem<R, M> {
fn from(reg: R) -> XmmMem<R, M> {
XmmMem::Xmm(reg)
}
}
impl<R: AsReg, M: AsReg> From<Amode<M>> for XmmMem<R, M> {
fn from(amode: Amode<M>) -> XmmMem<R, M> {
XmmMem::Mem(amode)
}
}
pub fn emit_modrm_sib_disp<R: AsReg>(
sink: &mut impl CodeSink,
enc_g: u8,
mem_e: &Amode<R>,
bytes_at_end: u8,
evex_scaling: Option<i8>,
) {
match *mem_e {
Amode::ImmReg { simm32, base, .. } => {
let enc_e = base.enc();
let mut imm = Disp::new(simm32.value(sink), evex_scaling);
let enc_e_low3 = enc_e & 7;
if enc_e_low3 == gpr::enc::RSP {
sink.put1(encode_modrm(imm.m0d(), enc_g & 7, 0b100));
sink.put1(0b00_100_100);
imm.emit(sink);
} else {
if enc_e_low3 == gpr::enc::RBP {
imm.force_immediate();
}
sink.put1(encode_modrm(imm.m0d(), enc_g & 7, enc_e & 7));
imm.emit(sink);
}
}
Amode::ImmRegRegShift {
simm32,
base,
index,
scale,
..
} => {
let enc_base = base.enc();
let enc_index = index.enc();
assert!(enc_index != gpr::enc::RSP);
let mut imm = Disp::new(simm32.value(), evex_scaling);
if enc_base & 7 == gpr::enc::RBP {
imm.force_immediate();
}
sink.put1(encode_modrm(imm.m0d(), enc_g & 7, 0b100));
sink.put1(encode_sib(scale.enc(), enc_index & 7, enc_base & 7));
imm.emit(sink);
}
Amode::RipRelative { target } => {
sink.put1(encode_modrm(0b00, enc_g & 7, 0b101));
sink.use_target(target);
sink.put4(-(i32::from(bytes_at_end)) as u32);
}
}
}