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, EvexLength, Vex, VexEscape, VexLength, 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
206 changes: 206 additions & 0 deletions cranelift/assembler-x64/meta/src/dsl/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,25 @@ pub fn vex(length: VexLength) -> Vex {
}
}

/// An abbreviated constructor for EVEX-encoded instructions.
#[must_use]
pub fn evex(length: EvexLength) -> 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 @@ -1176,3 +1194,191 @@ impl fmt::Display for Vex {
Ok(())
}
}

pub enum EvexLength {
L128,
L256,
L512,
}

impl EvexLength {
/// Encode the `L L'` bits.
pub fn bits(&self) -> (u8, u8) {
match self {
Self::L128 => (0b0, 0b0),
Self::L256 => (0b1, 0b0),
Self::L512 => (0b0, 0b1),
}
}
}

impl fmt::Display for EvexLength {
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"),
}
}
}

pub struct Evex {
/// The length of the operand (e.g., 128-bit, 256-bit, or 512-bit).
pub length: EvexLength,
/// 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 @@ -86,6 +86,8 @@ pub enum Feature {
lzcnt,
popcnt,
avx,
avx512f,
avx512vl,
cmpxchg16b,
}

Expand All @@ -110,6 +112,8 @@ pub const ALL_FEATURES: &[Feature] = &[
Feature::lzcnt,
Feature::popcnt,
Feature::avx,
Feature::avx512f,
Feature::avx512vl,
Feature::cmpxchg16b,
];

Expand Down
48 changes: 46 additions & 2 deletions cranelift/assembler-x64/meta/src/dsl/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,21 @@ impl Format {
pub fn uses_eflags(&self) -> bool {
self.eflags != Eflags::None
}

/// Returns the mask register if any operand uses masking
pub fn mask_register(&self) -> Option<u8> {
self.operands.iter().filter_map(|op| op.mask_reg).next()
}

/// Return the operand that uses a mask register
pub fn mask_register_operand(&self) -> Option<&Operand> {
self.operands.iter().find(|op| op.mask_reg.is_some())
}

/// Returns true if zeroing is used
pub fn zeroing(&self) -> bool {
self.operands.iter().any(|op| op.zeroing)
}
}

impl core::fmt::Display for Format {
Expand Down Expand Up @@ -214,7 +229,7 @@ impl core::fmt::Display for Format {
/// assert_eq!(sxq(imm32).to_string(), "imm32[sxq]");
/// assert_eq!(align(xmm_m128).to_string(), "xmm_m128[align]");
/// ```
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Operand {
/// The location of the data: memory, register, immediate.
pub location: Location,
Expand All @@ -229,6 +244,23 @@ pub struct Operand {
/// Some register operands are implicit: that is, they do not appear in the
/// disassembled output even though they are used in the instruction.
pub implicit: bool,
/// EVEX opmask register (k1-k7)
pub mask_reg: Option<u8>,
/// EVEX zeroing-masking flag (z)
pub zeroing: bool,
}

impl Operand {
pub fn k(mut self, reg: u8) -> Self {
assert!(reg >= 1 && reg <= 7, "Mask register must be k1-k7");
self.mask_reg = Some(reg);
self
}

pub fn z(mut self) -> Self {
self.zeroing = true;
self
}
}

impl core::fmt::Display for Operand {
Expand All @@ -239,6 +271,8 @@ impl core::fmt::Display for Operand {
extension,
align,
implicit,
mask_reg,
zeroing,
} = self;
write!(f, "{location}")?;
let mut flags = vec![];
Expand All @@ -254,6 +288,12 @@ impl core::fmt::Display for Operand {
if *implicit {
flags.push("implicit".to_owned());
}
if let Some(mask_reg) = mask_reg {
write!(f, "[k{mask_reg}]")?;
}
if *zeroing {
flags.push("z".to_owned());
}
if !flags.is_empty() {
write!(f, "[{}]", flags.join(","))?;
}
Expand All @@ -267,12 +307,16 @@ impl From<Location> for Operand {
let extension = Extension::default();
let align = false;
let implicit = false;
let mask_reg = None;
let zeroing = false;
Self {
location,
mutability,
extension,
align,
implicit,
mask_reg,
zeroing,
}
}
}
Expand All @@ -293,7 +337,7 @@ impl core::fmt::Display for RegClass {
}

/// An operand location, as expressed in Intel's _Instruction Set Reference_.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(non_camel_case_types, reason = "makes DSL definitions easier to read")]
pub enum Location {
// Fixed registers.
Expand Down
Loading