diff --git a/src/rust/iced-x86/src/code.rs b/src/rust/iced-x86/src/code.rs index 7271832d5..d35644ffd 100644 --- a/src/rust/iced-x86/src/code.rs +++ b/src/rust/iced-x86/src/code.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright (C) 2018-present iced project and contributors +use self::info_flags::OpAccessOptions; use crate::iced_constants::IcedConstants; use crate::iced_error::IcedError; #[cfg(feature = "instr_info")] @@ -45162,35 +45163,35 @@ impl Code { /// Gets operand #0's OpAccess #[must_use] #[inline] - pub const fn op0_access(&self) -> OpAccess { - self.info_flags1().op0_access() + pub const fn op0_access(&self, options: OpAccessOptions) -> OpAccess { + self.info_flags1().op0_access(options) } /// Gets operand #1's OpAccess #[must_use] #[inline] - pub const fn op1_access(&self) -> OpAccess { + pub const fn op1_access(&self, _options: OpAccessOptions) -> OpAccess { self.info_flags1().op1_access() } /// Gets operand #2's OpAccess #[must_use] #[inline] - pub const fn op2_access(&self) -> OpAccess { + pub const fn op2_access(&self, _options: OpAccessOptions) -> OpAccess { self.info_flags1().op2_access() } /// Gets operand #3's OpAccess #[must_use] #[inline] - pub const fn op3_access(&self) -> OpAccess { + pub const fn op3_access(&self, _options: OpAccessOptions) -> OpAccess { self.info_flags1().op3_access() } /// Gets operand #4's OpAccess #[must_use] #[inline] - pub const fn op4_access(&self) -> OpAccess { + pub const fn op4_access(&self, _options: OpAccessOptions) -> OpAccess { self.info_flags1().op4_access() } @@ -45200,13 +45201,13 @@ impl Code { /// * `operand`: Operand number. If the operand does not exist the function will return `OpAccess::None` #[must_use] #[inline] - pub const fn op_access(&self, operand: u32) -> OpAccess { + pub const fn op_access(&self, operand: u32, options: OpAccessOptions) -> OpAccess { match operand { - 0 => self.op0_access(), - 1 => self.op1_access(), - 2 => self.op2_access(), - 3 => self.op3_access(), - 4 => self.op4_access(), + 0 => self.op0_access(options), + 1 => self.op1_access(options), + 2 => self.op2_access(options), + 3 => self.op3_access(options), + 4 => self.op4_access(options), _ => OpAccess::None, } } diff --git a/src/rust/iced-x86/src/info/info_flags.rs b/src/rust/iced-x86/src/info/info_flags.rs index af8b6ff93..10dd4b024 100644 --- a/src/rust/iced-x86/src/info/info_flags.rs +++ b/src/rust/iced-x86/src/info/info_flags.rs @@ -4,7 +4,7 @@ use super::enums::{ }; use super::{Code, EncodingKind, OpAccess}; use crate::info_table::TABLE; -use core::mem; +use core::{fmt, mem}; pub(crate) const fn code_info_flags(code: Code) -> &'static (InfoFlags1, InfoFlags2) { // SAFETY: info_table::TABLE has a generated entry for each Code @@ -69,7 +69,7 @@ impl InfoFlags1 { unsafe { mem::transmute((self.0 & ((InfoFlags1Consts::OP_INFO4_MASK) << InfoFlags1Consts::OP_INFO4_SHIFT)) != 0) } } - pub const fn op0_access(&self) -> OpAccess { + pub const fn op0_access(&self, config: OpAccessOptions) -> OpAccess { match self.op0_info() { OpInfo0::None => OpAccess::None, OpInfo0::Read => OpAccess::Read, @@ -95,7 +95,13 @@ impl InfoFlags1 { // Cmovge_r32_rm32 // Cmovle_r32_rm32 // Cmovg_r32_rm32 - OpInfo0::CondWrite32_ReadWrite64 => OpAccess::CondWrite, + OpInfo0::CondWrite32_ReadWrite64 => { + if config.is_64_set() { + OpAccess::ReadWrite + } else { + OpAccess::CondWrite + } + } OpInfo0::ReadWrite => OpAccess::ReadWrite, OpInfo0::ReadWriteVmm => OpAccess::ReadWrite, OpInfo0::ReadCondWrite => OpAccess::ReadCondWrite, @@ -111,7 +117,13 @@ impl InfoFlags1 { // "Legacy version: When the source and destination operands are XMM registers, bits (MAXVL-1:32) of the corresponding destination register are unmodified. When the source operand is a memory location and destination operand is an XMM registers, Bits (127:32) of the destination operand is cleared to all 0s, bits MAXVL:128 of the destination operand remains unchanged." // When the operands are availale it is possible to decide whether this is // `OpAccess::Write` or `OpAccess::ReadWrite` here we return the more general one. - OpInfo0::WriteMem_ReadWriteReg => OpAccess::ReadWrite, + OpInfo0::WriteMem_ReadWriteReg => { + if config.has_memory_operand_set() { + OpAccess::Write + } else { + OpAccess::ReadWrite + } + } } } @@ -137,3 +149,106 @@ impl InfoFlags1 { } } } + +/// Controls behaviour of [`Code::op_access`] functions. +#[derive(Default, Clone, Copy)] +pub struct OpAccessOptions(u8); + +impl fmt::Debug for OpAccessOptions { + #[allow(clippy::missing_inline_in_public_items)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OpAccessOptions").field("has_memory_operand", &self.has_memory_operand_set()).field("is_64", &self.is_64_set()).finish() + } +} + +impl OpAccessOptions { + const HAS_MEMORY_OPERAND_SHIFT: u8 = 0; + const IS_64_SHIFT: u8 = 1; + + const HAS_MEMORY_OPERAND_MASK: u8 = 1u8 << Self::HAS_MEMORY_OPERAND_SHIFT; + const IS_64_MASK: u8 = 1u8 << Self::IS_64_SHIFT; + + /// Set whether it should be assumed that there is at least one memory operand when calculating operand access. Currently this + /// affects operand 0 of the following [`Code`]-s: + /// + /// ``` + /// # use iced_x86::Code; + /// # use iced_x86::Code::*; + /// # use iced_x86::OpAccessOptions; + /// # + /// let affected_codes = [ + /// Movss_xmm_xmmm32, Movsd_xmm_xmmm64, Movss_xmmm32_xmm, Movsd_xmmm64_xmm, + /// ]; + /// let config = OpAccessOptions::default(); + /// let config_memory = OpAccessOptions::default().has_memory_operand(true); + /// for i in 0..=4 { + /// for code in Code::values() { + /// if affected_codes.contains(&code) && i == 0 { + /// assert_ne!(code.op_access(i, config), code.op_access(i, config_memory)); + /// } else { + /// assert_eq!(code.op_access(i, config), code.op_access(i, config_memory)); + /// } + /// } + /// } + /// ``` + /// + /// Relevant part from the intel manual for `movss`: + /// > Legacy version: When the source and destination operands are XMM registers, bits (MAXVL-1:32) of the corresponding destination register are unmodified. When the source operand is a memory location and destination operand is an XMM registers, Bits (127:32) of the destination operand is cleared to all 0s, bits MAXVL:128 of the destination operand remains unchanged. + /// + /// When there are only register operands, operand 0 has `OpAccess::ReadWrite`. If there is a memory operand, the access is `OpAccess::Write` + #[must_use] + #[inline] + pub const fn has_memory_operand(self, value: bool) -> Self { + if value { + Self(self.0 | Self::HAS_MEMORY_OPERAND_MASK) + } else { + Self(self.0 & !Self::HAS_MEMORY_OPERAND_MASK) + } + } + + /// Set whether the bitness should be 64 when calculating operand access. Currently this + /// affects operand 0 the following [`Code`]-s: + /// ``` + /// # use iced_x86::Code; + /// # use iced_x86::Code::*; + /// # use iced_x86::OpAccessOptions; + /// # + /// let affected_codes = [ + /// Cmovo_r32_rm32, Cmovno_r32_rm32, Cmovb_r32_rm32, Cmovae_r32_rm32, + /// Cmove_r32_rm32, Cmovne_r32_rm32, Cmovbe_r32_rm32, Cmova_r32_rm32, + /// Cmovs_r32_rm32, Cmovns_r32_rm32, Cmovp_r32_rm32, Cmovnp_r32_rm32, + /// Cmovl_r32_rm32, Cmovge_r32_rm32, Cmovle_r32_rm32, Cmovg_r32_rm32, + /// ]; + /// let config = OpAccessOptions::default(); + /// let config_64 = OpAccessOptions::default().is_64(true); + /// for i in 0..=4 { + /// for code in Code::values() { + /// if affected_codes.contains(&code) && i == 0 { + /// assert_ne!(code.op_access(i, config), code.op_access(i, config_64)); + /// } else { + /// assert_eq!(code.op_access(i, config), code.op_access(i, config_64)); + /// } + /// } + /// } + /// ``` + /// + /// In 64 bit mode [`OpAccess::ReadWrite`] is returned, since the upper 32 bits are always + /// zeroed. In other modes [`OpAccess::CondWrite`] is returned. + #[must_use] + #[inline] + pub const fn is_64(self, value: bool) -> Self { + if value { + Self(self.0 | Self::IS_64_MASK) + } else { + Self(self.0 & !Self::IS_64_MASK) + } + } + + const fn has_memory_operand_set(&self) -> bool { + self.0 & Self::HAS_MEMORY_OPERAND_MASK != 0 + } + + const fn is_64_set(&self) -> bool { + self.0 & Self::IS_64_MASK != 0 + } +} diff --git a/src/rust/iced-x86/src/info/info_table.rs b/src/rust/iced-x86/src/info/info_table.rs index d0bedd9a7..9c8724d0f 100644 --- a/src/rust/iced-x86/src/info/info_table.rs +++ b/src/rust/iced-x86/src/info/info_table.rs @@ -3,6 +3,8 @@ // ⚠️This file was generated by GENERATOR!🦹‍♂️ +// We must allow this to use `Code::op_access` in const contexts +#[allow(clippy::large_const_arrays)] #[rustfmt::skip] pub(crate) const TABLE: [(u32, u32); 4936] = [ (0x0000_0000, 0x0090_0000),// INVALID diff --git a/src/rust/iced-x86/src/info/mod.rs b/src/rust/iced-x86/src/info/mod.rs index e31691e5d..081fb5675 100644 --- a/src/rust/iced-x86/src/info/mod.rs +++ b/src/rust/iced-x86/src/info/mod.rs @@ -15,6 +15,7 @@ pub use crate::info::factory::*; use crate::*; use alloc::vec::Vec; use core::fmt; +pub use info_flags::OpAccessOptions; /// A register used by an instruction #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]