Skip to content

x64: EVEX Encoding for new assembler #11153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 16, 2025
Merged
2 changes: 1 addition & 1 deletion cranelift/assembler-x64/meta/src/dsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ pub mod format;

pub use custom::{Custom, Customization};
pub use encoding::{Encoding, ModRmKind, OpcodeMod};
pub use encoding::{Evex, Length, Vex, VexEscape, VexPrefix, evex, vex};
pub use encoding::{
Group1Prefix, Group2Prefix, Group3Prefix, Group4Prefix, Opcodes, Prefixes, Rex, rex,
};
pub use encoding::{Vex, VexEscape, VexLength, VexPrefix, vex};
pub use features::{ALL_FEATURES, Feature, Features};
pub use format::{Eflags, Extension, Format, Location, Mutability, Operand, OperandKind, RegClass};
pub use format::{align, fmt, implicit, r, rw, sxl, sxq, sxw, w};
Expand Down
231 changes: 215 additions & 16 deletions cranelift/assembler-x64/meta/src/dsl/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn rex(opcode: impl Into<Opcodes>) -> Rex {

/// An abbreviated constructor for VEX-encoded instructions.
#[must_use]
pub fn vex(length: VexLength) -> Vex {
pub fn vex(length: Length) -> Vex {
Vex {
length,
pp: None,
Expand All @@ -45,10 +45,25 @@ pub fn vex(length: VexLength) -> Vex {
}
}

/// An abbreviated constructor for EVEX-encoded instructions.
#[must_use]
pub fn evex(length: Length) -> Evex {
Evex {
length,
pp: None,
mmmmm: None,
w: VexW::WIG,
opcode: u8::MAX,
modrm: None,
imm: Imm::None,
}
}

/// Enumerate the ways x64 encodes instructions.
pub enum Encoding {
Rex(Rex),
Vex(Vex),
Evex(Evex),
}

impl Encoding {
Expand All @@ -58,6 +73,7 @@ impl Encoding {
match self {
Encoding::Rex(rex) => rex.validate(operands),
Encoding::Vex(vex) => vex.validate(operands),
Encoding::Evex(evex) => evex.validate(operands),
}
}

Expand All @@ -66,6 +82,7 @@ impl Encoding {
match self {
Encoding::Rex(rex) => rex.opcodes.opcode(),
Encoding::Vex(vex) => vex.opcode,
Encoding::Evex(evex) => evex.opcode,
}
}
}
Expand All @@ -75,6 +92,7 @@ impl fmt::Display for Encoding {
match self {
Encoding::Rex(rex) => write!(f, "{rex}"),
Encoding::Vex(vex) => write!(f, "{vex}"),
Encoding::Evex(evex) => write!(f, "{evex}"),
}
}
}
Expand Down Expand Up @@ -841,40 +859,60 @@ impl fmt::Display for VexEscape {
}
}

/// Contains allowed VEX length definitions.
/// Contains vector length definitions.
///
/// VEX encodes these in the `L` bit field, a single bit with `128-bit = 0` and
/// `256-bit = 1`. For convenience, we also include the `LIG` and `LZ`
/// syntax, used by the reference manual, and always set these to `0`.
pub enum VexLength {
/// Set `VEX.L` to `0` (128-bit).
/// `256-bit = 1`. For convenience, we also include the `LIG` and `LZ` syntax,
/// used by the reference manual, and always set these to `0`.
///
/// EVEX encodes this in the `L'L` bits, two bits that typically indicate the
/// vector length for packed vector instructions but can also be used for
/// rounding control for floating-point instructions with rounding semantics
/// (see section 2.7.1 in the reference manual).
pub enum Length {
/// 128-bit vector length.
L128,
/// Set `VEX.L` to `1` (256-bit).
/// 256-bit vector length.
L256,
/// Set `VEX.L` to `0`, but not necessarily for 128-bit operation. From the
/// reference manual: "The VEX.L must be encoded to be 0B, an #UD occurs if
/// VEX.L is not zero."
/// 512-bit vector length; invalid for VEX instructions.
L512,
/// Force the length bits to `0`, but not necessarily for 128-bit operation.
/// From the reference manual: "The VEX.L must be encoded to be 0B, an #UD
/// occurs if VEX.L is not zero."
LZ,
/// The `VEX.L` bit is ignored (e.g., for floating point scalar
/// The length bits are ignored (e.g., for floating point scalar
/// instructions). This assembler will emit `0`.
LIG,
}

impl VexLength {
/// Encode the `L` bit.
pub fn bits(&self) -> u8 {
impl Length {
/// Encode the `VEX.L` bit.
pub fn vex_bits(&self) -> u8 {
match self {
Self::L128 | Self::LIG | Self::LZ => 0b0,
Self::L256 => 0b1,
Self::L512 => unreachable!("VEX does not support 512-bit vector length"),
}
}

/// Encode the `EVEX.L'L` bits.
///
/// See section 2.7.10, Vector Length Orthogonality, in the reference manual
pub fn evex_bits(&self) -> u8 {
match self {
Self::L128 | Self::LIG | Self::LZ => 0b00,
Self::L256 => 0b01,
Self::L512 => 0b10,
}
}
}

impl fmt::Display for VexLength {
impl fmt::Display for Length {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::L128 => write!(f, "128"),
Self::L256 => write!(f, "256"),
Self::L512 => write!(f, "512"),
Self::LIG => write!(f, "LIG"),
Self::LZ => write!(f, "LZ"),
}
Expand Down Expand Up @@ -930,7 +968,7 @@ impl fmt::Display for VexW {
/// ```
pub struct Vex {
/// The length of the operand (e.g., 128-bit or 256-bit).
pub length: VexLength,
pub length: Length,
/// Map the `PP` field encodings.
pub pp: Option<VexPrefix>,
/// Map the `MMMMM` field encodings.
Expand Down Expand Up @@ -1176,3 +1214,164 @@ impl fmt::Display for Vex {
Ok(())
}
}

pub struct Evex {
/// The vector length of the operand (e.g., 128-bit, 256-bit, or 512-bit).
pub length: Length,
/// Map the `PP` field encodings.
pub pp: Option<VexPrefix>,
/// Map the `MMMMM` field encodings.
pub mmmmm: Option<VexEscape>,
/// The `W` bit.
pub w: VexW,
/// EVEX-encoded instructions opcode byte"
pub opcode: u8,
/// See [`Rex.modrm`](Rex.modrm).
pub modrm: Option<ModRmKind>,
/// See [`Rex.imm`](Rex.imm).
pub imm: Imm,
}

impl Evex {
/// Set the `pp` field to use [`VexPrefix::_66`]; equivalent to `.66` in the
/// manual.
pub fn _66(self) -> Self {
assert!(self.pp.is_none());
Self {
pp: Some(VexPrefix::_66),
..self
}
}

/// Set the `pp` field to use [`VexPrefix::_F2`]; equivalent to `.F2` in the
/// manual.
pub fn _f2(self) -> Self {
assert!(self.pp.is_none());
Self {
pp: Some(VexPrefix::_F2),
..self
}
}

/// Set the `pp` field to use [`VexPrefix::_F3`]; equivalent to `.F3` in the
/// manual.
pub fn _f3(self) -> Self {
assert!(self.pp.is_none());
Self {
pp: Some(VexPrefix::_F3),
..self
}
}

/// Set the `mmmmmm` field to use [`VexEscape::_0F`]; equivalent to `.0F` in
/// the manual.
pub fn _0f(self) -> Self {
assert!(self.mmmmm.is_none());
Self {
mmmmm: Some(VexEscape::_0F),
..self
}
}

/// Set the `mmmmmm` field to use [`VexEscape::_0F3A`]; equivalent to
/// `.0F3A` in the manual.
pub fn _0f3a(self) -> Self {
assert!(self.mmmmm.is_none());
Self {
mmmmm: Some(VexEscape::_0F3A),
..self
}
}

/// Set the `mmmmmm` field to use [`VexEscape::_0F38`]; equivalent to
/// `.0F38` in the manual.
pub fn _0f38(self) -> Self {
assert!(self.mmmmm.is_none());
Self {
mmmmm: Some(VexEscape::_0F38),
..self
}
}

/// Set the `W` bit to `0`; equivalent to `.W0` in the manual.
pub fn w0(self) -> Self {
assert!(self.w.is_ignored());
Self {
w: VexW::W0,
..self
}
}

/// Set the `W` bit to `1`; equivalent to `.W1` in the manual.
pub fn w1(self) -> Self {
assert!(self.w.is_ignored());
Self {
w: VexW::W1,
..self
}
}

/// Ignore the `W` bit; equivalent to `.WIG` in the manual.
pub fn wig(self) -> Self {
assert!(self.w.is_ignored());
Self {
w: VexW::WIG,
..self
}
}

/// Set the single opcode for this VEX-encoded instruction.
pub fn op(self, opcode: u8) -> Self {
assert_eq!(self.opcode, u8::MAX);
Self { opcode, ..self }
}

/// Set the ModR/M byte to contain a register operand; see [`Rex::r`].
pub fn r(self) -> Self {
assert!(self.modrm.is_none());
Self {
modrm: Some(ModRmKind::Reg),
..self
}
}

fn validate(&self, _operands: &[Operand]) {
assert!(self.opcode != u8::MAX);
assert!(self.mmmmm.is_some());
}

/// Retrieve the digit extending the opcode, if available.
#[must_use]
pub fn unwrap_digit(&self) -> Option<u8> {
match self.modrm {
Some(ModRmKind::Digit(digit)) => Some(digit),
_ => None,
}
}
}

impl From<Evex> for Encoding {
fn from(evex: Evex) -> Encoding {
Encoding::Evex(evex)
}
}

impl fmt::Display for Evex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "EVEX.{}", self.length)?;
if let Some(pp) = self.pp {
write!(f, ".{pp}")?;
}
if let Some(mmmmm) = self.mmmmm {
write!(f, ".{mmmmm}")?;
}
write!(f, ".{} {:#04X}", self.w, self.opcode)?;
if let Some(modrm) = self.modrm {
write!(f, " {modrm}")?;
}
if self.imm != Imm::None {
write!(f, " {}", self.imm)?;
}
Ok(())
}
}
4 changes: 4 additions & 0 deletions cranelift/assembler-x64/meta/src/dsl/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ pub enum Feature {
popcnt,
avx,
avx2,
avx512f,
avx512vl,
cmpxchg16b,
fma,
}
Expand All @@ -111,6 +113,8 @@ pub const ALL_FEATURES: &[Feature] = &[
Feature::popcnt,
Feature::avx,
Feature::avx2,
Feature::avx512f,
Feature::avx512vl,
Feature::cmpxchg16b,
Feature::fma,
];
Expand Down
Loading
Loading